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 
17 #define TAG "NativeMediaDrm"
18 
19 #include <utils/Log.h>
20 #include <sys/types.h>
21 
22 #include <list>
23 #include <string>
24 #include <vector>
25 
26 #include <assert.h>
27 #include <jni.h>
28 #include <nativehelper/JNIHelp.h>
29 
30 #include <android/native_window_jni.h>
31 
32 #include "AMediaObjects.h"
33 
34 #include "media/NdkMediaCodec.h"
35 #include "media/NdkMediaCrypto.h"
36 #include "media/NdkMediaDrm.h"
37 #include "media/NdkMediaExtractor.h"
38 #include "media/NdkMediaFormat.h"
39 #include "media/NdkMediaMuxer.h"
40 
41 typedef std::vector<uint8_t> Uuid;
42 
43 struct fields_t {
44     jfieldID surface;
45     jfieldID mimeType;
46     jfieldID audioUrl;
47     jfieldID videoUrl;
48 };
49 
50 struct PlaybackParams {
51     jobject surface;
52     jstring mimeType;
53     jstring audioUrl;
54     jstring videoUrl;
55 };
56 
57 static fields_t gFieldIds;
58 static bool gGotVendorDefinedEvent = false;
59 static bool gListenerGotValidExpiryTime = false;
60 static bool gOnKeyChangeListenerOK = false;
61 
62 static const size_t kPlayTimeSeconds = 30;
63 static const size_t kUuidSize = 16;
64 
65 static const uint8_t kClearKeyUuid[kUuidSize] = {
66     0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
67     0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
68 };
69 
70 // The test content is not packaged with clearkey UUID,
71 // we have to use a canned clearkey pssh for the test.
72 static const uint8_t kClearkeyPssh[] = {
73     // BMFF box header (4 bytes size + 'pssh')
74     0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
75     // full box header (version = 1 flags = 0)
76     0x01, 0x00, 0x00, 0x00,
77     // system id
78     0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
79     0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
80     // number of key ids
81     0x00, 0x00, 0x00, 0x01,
82     // key id
83     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
84     0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
85     // size of data, must be zero
86     0x00, 0x00, 0x00, 0x00
87 };
88 
89 static const uint8_t kKeyRequestData[] = {
90     0x7b, 0x22, 0x6b, 0x69, 0x64,
91     0x73, 0x22, 0x3a, 0x5b, 0x22,
92     0x4d, 0x44, 0x41, 0x77, 0x4d,
93     0x44, 0x41, 0x77, 0x4d, 0x44,
94     0x41, 0x77, 0x4d, 0x44, 0x41,
95     0x77, 0x4d, 0x44, 0x41, 0x77,
96     0x4d, 0x41, 0x22, 0x5d, 0x2c,
97     0x22, 0x74, 0x79, 0x70, 0x65,
98     0x22, 0x3a, 0x22, 0x74, 0x65,
99     0x6d, 0x70, 0x6f, 0x72, 0x61,
100     0x72, 0x79, 0x22, 0x7d
101 };
102 
103 static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
104 
105 // base 64 encoded JSON response string, must not contain padding character '='
106 static const char kResponse[] = "{\"keys\":[{\"kty\":\"oct\"," \
107         "\"kid\":\"MDAwMDAwMDAwMDAwMDAwMA\",\"k\":" \
108         "\"Pwoz80CYueIrwHjgobXoVA\"}]}";
109 
isUuidSizeValid(Uuid uuid)110 static bool isUuidSizeValid(Uuid uuid) {
111     return (uuid.size() == kUuidSize);
112 }
113 
jbyteArrayToVector(JNIEnv * env,jbyteArray const & byteArray)114 static std::vector<uint8_t> jbyteArrayToVector(
115     JNIEnv* env, jbyteArray const &byteArray) {
116     uint8_t* buffer = reinterpret_cast<uint8_t*>(
117         env->GetByteArrayElements(byteArray, /*is_copy*/NULL));
118     std::vector<uint8_t> vector;
119     for (jsize i = 0; i < env->GetArrayLength(byteArray); ++i) {
120         vector.push_back(buffer[i]);
121     }
122     return vector;
123 }
124 
jbyteArrayToUuid(JNIEnv * env,jbyteArray const & uuid)125 static Uuid jbyteArrayToUuid(JNIEnv* env, jbyteArray const &uuid) {
126     Uuid juuid;
127     juuid.resize(0);
128     if (uuid != NULL) {
129         juuid = jbyteArrayToVector(env, uuid);
130     }
131     return juuid;
132 }
133 
Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(JNIEnv * env,jclass,jbyteArray uuid)134 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(
135     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
136 
137     if (NULL == uuid) {
138         jniThrowException(env, "java/lang/NullPointerException", "null uuid");
139         return JNI_FALSE;
140     }
141 
142     Uuid juuid = jbyteArrayToUuid(env, uuid);
143     if (isUuidSizeValid(juuid)) {
144          return AMediaDrm_isCryptoSchemeSupported(&juuid[0], NULL);
145     } else {
146           jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
147                   "invalid UUID size, expected %u bytes", kUuidSize);
148     }
149     return JNI_FALSE;
150 }
151 
initPlaybackParams(JNIEnv * env,const jobject & playbackParams,PlaybackParams & params)152 void initPlaybackParams(JNIEnv* env, const jobject &playbackParams, PlaybackParams &params) {
153     params.surface = env->GetObjectField(
154         playbackParams, gFieldIds.surface);
155 
156     params.mimeType = static_cast<jstring>(env->GetObjectField(
157         playbackParams, gFieldIds.mimeType));
158 
159     params.audioUrl = static_cast<jstring>(env->GetObjectField(
160         playbackParams, gFieldIds.audioUrl));
161 
162     params.videoUrl = static_cast<jstring>(env->GetObjectField(
163         playbackParams, gFieldIds.videoUrl));
164 }
165 
Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(JNIEnv * env,jclass clazz,jbyteArray uuid,jstring name,jobject outValue)166 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(
167     JNIEnv* env, jclass clazz, jbyteArray uuid,
168     jstring name, jobject outValue) {
169 
170     if (NULL == uuid || NULL == name || NULL == outValue) {
171         jniThrowException(env, "java/lang/NullPointerException",
172                 "One or more null input parameters");
173         return JNI_FALSE;
174     }
175 
176     Uuid juuid = jbyteArrayToUuid(env, uuid);
177     if (!isUuidSizeValid(juuid)) {
178         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
179                 "invalid UUID size, expected %u bytes", kUuidSize);
180         return JNI_FALSE;
181     }
182 
183     AMediaObjects aMediaObjects;
184     aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
185     if (NULL == aMediaObjects.getDrm()) {
186         jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
187         return JNI_FALSE;
188     }
189 
190     const char *utf8_name = env->GetStringUTFChars(name, NULL);
191     const char *utf8_outValue = NULL;
192     media_status_t status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
193             utf8_name, &utf8_outValue);
194     env->ReleaseStringUTFChars(name, utf8_name);
195 
196     if (NULL != utf8_outValue) {
197         clazz = env->GetObjectClass(outValue);
198         jmethodID mId = env->GetMethodID (clazz, "append",
199                 "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
200         jstring outString = env->NewStringUTF(
201                 static_cast<const char *>(utf8_outValue));
202         env->CallObjectMethod(outValue, mId, outString);
203     } else {
204         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
205                 "get property string returns %d", status);
206         return JNI_FALSE;
207     }
208     return JNI_TRUE;
209 }
210 
Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(JNIEnv * env,jclass,jbyteArray uuid)211 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(
212         JNIEnv* env, jclass /* clazz */, jbyteArray uuid) {
213 
214     if (NULL == uuid) {
215         jniThrowException(env, "java/lang/NullPointerException",
216                 "uuid is NULL");
217         return JNI_FALSE;
218     }
219 
220     Uuid juuid = jbyteArrayToUuid(env, uuid);
221     if (!isUuidSizeValid(juuid)) {
222         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
223                 "invalid UUID size, expected %u bytes", kUuidSize);
224         return JNI_FALSE;
225     }
226 
227     AMediaDrm* drm = AMediaDrm_createByUUID(&juuid[0]);
228     const char *propertyName = "clientId";
229     const uint8_t value[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
230     media_status_t status = AMediaDrm_setPropertyByteArray(drm, propertyName, value, sizeof(value));
231     if (status != AMEDIA_OK) {
232         jniThrowException(env, "java/lang/RuntimeException", "setPropertyByteArray failed");
233         AMediaDrm_release(drm);
234         return JNI_FALSE;
235     }
236     AMediaDrmByteArray array;
237     status = AMediaDrm_getPropertyByteArray(drm, propertyName, &array);
238     if (status != AMEDIA_OK) {
239         jniThrowException(env, "java/lang/RuntimeException", "getPropertyByteArray failed");
240         AMediaDrm_release(drm);
241         return JNI_FALSE;
242     }
243     if (array.length != sizeof(value)) {
244         jniThrowException(env, "java/lang/RuntimeException", "byte array size differs");
245         AMediaDrm_release(drm);
246         return JNI_FALSE;
247     }
248     if (!array.ptr) {
249         jniThrowException(env, "java/lang/RuntimeException", "byte array pointer is null");
250         AMediaDrm_release(drm);
251         return JNI_FALSE;
252     }
253     if (memcmp(array.ptr, value, sizeof(value)) != 0) {
254         jniThrowException(env, "java/lang/RuntimeException", "byte array content differs");
255         AMediaDrm_release(drm);
256         return JNI_FALSE;
257     }
258     AMediaDrm_release(drm);
259     return JNI_TRUE;
260 }
261 
Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(JNIEnv * env,jclass,jbyteArray uuid,jstring videoUrl)262 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(
263     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
264 
265     if (NULL == uuid || NULL == videoUrl) {
266         jniThrowException(env, "java/lang/NullPointerException",
267                 "null uuid or null videoUrl");
268         return JNI_FALSE;
269     }
270 
271     Uuid juuid = jbyteArrayToUuid(env, uuid);
272     if (!isUuidSizeValid(juuid)) {
273         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
274                 "invalid UUID size, expected %u bytes", kUuidSize);
275         return JNI_FALSE;
276     }
277 
278     AMediaObjects aMediaObjects;
279     aMediaObjects.setVideoExtractor(AMediaExtractor_new());
280     const char* url = env->GetStringUTFChars(videoUrl, 0);
281     if (url) {
282         media_status_t status = AMediaExtractor_setDataSource(
283             aMediaObjects.getVideoExtractor(), url);
284         env->ReleaseStringUTFChars(videoUrl, url);
285 
286         if (status != AMEDIA_OK) {
287             jniThrowExceptionFmt(env, "java/lang/RuntimeException",
288                     "set video data source error=%d", status);
289             return JNI_FALSE;
290         }
291     }
292 
293     PsshInfo* psshInfo = AMediaExtractor_getPsshInfo(aMediaObjects.getVideoExtractor());
294     if (psshInfo == NULL) {
295         jniThrowException(env, "java/lang/RuntimeException", "null psshInfo");
296         return JNI_FALSE;
297     }
298 
299     jboolean testResult = JNI_FALSE;
300     for (size_t i = 0; i < psshInfo->numentries; i++) {
301         PsshEntry *entry = &psshInfo->entries[i];
302 
303         if (0 == memcmp(entry->uuid, kClearKeyUuid, sizeof(entry->uuid))) {
304             aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
305             if (aMediaObjects.getDrm()) {
306                 testResult = JNI_TRUE;
307             } else {
308                 ALOGE("Failed to create media drm=%zd", i);
309                 testResult = JNI_FALSE;
310             }
311             break;
312         }
313     }
314     return testResult;
315 }
316 
isVideo(const char * mime)317 static bool isVideo(const char* mime) {
318     return !strncmp(mime, "video/", 6) ? true : false;
319 }
320 
isAudio(const char * mime)321 static bool isAudio(const char* mime) {
322     return !strncmp(mime, "audio/", 6) ? true : false;
323 }
324 
addTrack(const AMediaFormat * format,const char * mime,const AMediaCrypto * crypto,const ANativeWindow * window,AMediaCodec ** codec)325 static void addTrack(const AMediaFormat* format,
326         const char* mime, const AMediaCrypto* crypto,
327         const ANativeWindow* window, AMediaCodec** codec) {
328 
329     *codec = AMediaCodec_createDecoderByType(mime);
330     if (codec == NULL) {
331         ALOGE("cannot create codec for %s", mime);
332         return;
333     }
334 
335     AMediaCodec_configure(*codec, format,
336             const_cast<ANativeWindow*>(window),
337             const_cast<AMediaCrypto*>(crypto), 0);
338 }
339 
addTracks(const AMediaExtractor * extractor,const AMediaCrypto * crypto,const ANativeWindow * window,AMediaCodec ** codec)340 static void addTracks(const AMediaExtractor* extractor,
341         const AMediaCrypto* crypto, const ANativeWindow* window,
342         AMediaCodec** codec) {
343     size_t numTracks = AMediaExtractor_getTrackCount(
344         const_cast<AMediaExtractor*>(extractor));
345 
346     AMediaFormat* trackFormat = NULL;
347     for (size_t i = 0; i < numTracks; ++i) {
348         trackFormat = AMediaExtractor_getTrackFormat(
349             const_cast<AMediaExtractor*>(extractor), i);
350         if (trackFormat) {
351             ALOGV("track %zd format: %s", i,
352                     AMediaFormat_toString(trackFormat));
353 
354             const char* mime = "";
355             if (!AMediaFormat_getString(
356                 trackFormat, AMEDIAFORMAT_KEY_MIME, &mime)) {
357                 ALOGE("no mime type");
358 
359                 AMediaFormat_delete(trackFormat);
360                 return;
361             } else if (isAudio(mime) || isVideo(mime)) {
362                 AMediaExtractor_selectTrack(
363                     const_cast<AMediaExtractor*>(extractor), i);
364                 ALOGV("track %zd codec format: %s", i,
365                         AMediaFormat_toString(trackFormat));
366 
367                 addTrack(trackFormat, mime, crypto, window, codec);
368                 AMediaCodec_start(*codec);
369                 AMediaCodec_flush(*codec);
370                 AMediaExtractor_seekTo(
371                     const_cast<AMediaExtractor*>(extractor), 0,
372                             AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
373             }
374             AMediaFormat_delete(trackFormat);
375         }
376     }
377 }
378 
getSystemNanoTime()379 static int64_t getSystemNanoTime() {
380     timespec now;
381     clock_gettime(CLOCK_MONOTONIC, &now);
382     return now.tv_sec * 1000000000LL + now.tv_nsec;
383 }
384 
fillDecoder(AMediaCodec * codec,AMediaExtractor * extractor,int64_t * presentationTimeUs,bool * eosReached)385 static void fillDecoder(AMediaCodec* codec, AMediaExtractor* extractor,
386         int64_t* presentationTimeUs, bool* eosReached) {
387     media_status_t status = AMEDIA_OK;
388 
389     ssize_t bufferIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
390     if (bufferIndex >= 0) {
391         size_t bufsize;
392         uint8_t* buf = AMediaCodec_getInputBuffer(codec, bufferIndex, &bufsize);
393 
394         int sampleSize = AMediaExtractor_readSampleData(extractor, buf, bufsize);
395         if (sampleSize < 0) {
396             sampleSize = 0;
397             *eosReached = true;
398         }
399 
400         *presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
401 
402         AMediaCodecCryptoInfo *cryptoInfo =
403             AMediaExtractor_getSampleCryptoInfo(extractor);
404 
405         if (cryptoInfo) {
406             status = AMediaCodec_queueSecureInputBuffer(
407                 codec, bufferIndex, 0, cryptoInfo,
408                 *presentationTimeUs,
409                 *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
410             AMediaCodecCryptoInfo_delete(cryptoInfo);
411         } else {
412             status = AMediaCodec_queueInputBuffer(
413                 codec, bufferIndex, 0, sampleSize,
414                 *presentationTimeUs,
415                 *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
416         }
417         AMediaExtractor_advance(extractor);
418     }
419 }
420 
drainDecoder(AMediaCodec * codec,int64_t presentationTimeUs,int64_t * startTimeNano)421 static bool drainDecoder(AMediaCodec* codec, int64_t presentationTimeUs,
422     int64_t* startTimeNano) {
423 
424     AMediaCodecBufferInfo info;
425     ssize_t bufferIndex  = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
426     if (bufferIndex >= 0) {
427         if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
428             return true;  // eos reached
429         }
430 
431         if (*startTimeNano < 0) {
432             *startTimeNano = getSystemNanoTime() - (presentationTimeUs * 1000);
433         }
434         int64_t delay = (*startTimeNano + presentationTimeUs * 1000) -
435                 getSystemNanoTime();
436         if (delay > 0) {
437             usleep(delay / 1000);
438         }
439 
440         AMediaCodec_releaseOutputBuffer(codec, bufferIndex, info.size != 0);
441     } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
442         ALOGV("output buffers changed");
443     } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
444         AMediaFormat* format = AMediaCodec_getOutputFormat(codec);
445         ALOGV("format changed to: %s", AMediaFormat_toString(format));
446         AMediaFormat_delete(format);
447     } else if (bufferIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
448          ALOGV("no output buffer right now");
449          usleep(20000);
450     } else {
451          ALOGV("unexpected info code: %zd", bufferIndex);
452     }
453     return false;
454 }
455 
playContent(JNIEnv * env,const AMediaObjects & aMediaObjects,PlaybackParams & params,const AMediaDrmSessionId & sessionId,Uuid uuid)456 static jboolean playContent(JNIEnv* env, const AMediaObjects& aMediaObjects,
457         PlaybackParams& params, const AMediaDrmSessionId& sessionId, Uuid uuid) {
458 
459     ANativeWindow *window = ANativeWindow_fromSurface(env, params.surface);
460     AMediaExtractor* audioExtractor = aMediaObjects.getAudioExtractor();
461     AMediaExtractor* videoExtractor = aMediaObjects.getVideoExtractor();
462 
463     AMediaCodec* audioCodec = NULL;
464     AMediaCodec* videoCodec = NULL;
465     AMediaCrypto* crypto = NULL;
466 
467     crypto = AMediaCrypto_new(&uuid[0], sessionId.ptr, sessionId.length);
468     if (crypto == NULL) {
469         jniThrowException(env, "java/lang/RuntimeException",
470                 "failed to create crypto object");
471         return JNI_FALSE;
472     }
473 
474     addTracks(audioExtractor, NULL, NULL, &audioCodec);
475 
476     addTracks(videoExtractor, crypto, window, &videoCodec);
477 
478     bool sawAudioInputEos = false;
479     bool sawAudioOutputEos = false;
480     bool sawVideoInputEos = false;
481     bool sawVideoOutputEos = false;
482     int64_t videoPresentationTimeUs = 0;
483     int64_t videoStartTimeNano = -1;
484     struct timespec timeSpec;
485     clock_gettime(CLOCK_MONOTONIC, &timeSpec);
486     time_t startTimeSec = timeSpec.tv_sec;
487 
488     while (!sawAudioOutputEos && !sawVideoOutputEos) {
489         if (!sawVideoInputEos) {
490             fillDecoder(videoCodec, videoExtractor,
491                     &videoPresentationTimeUs, &sawVideoInputEos);
492         }
493 
494         if (!sawAudioInputEos) {
495             // skip audio, still need to advance the audio extractor
496             AMediaExtractor_advance(audioExtractor);
497         }
498 
499         if (!sawVideoOutputEos) {
500             sawVideoOutputEos = drainDecoder(videoCodec, videoPresentationTimeUs,
501                     &videoStartTimeNano);
502         }
503 
504         clock_gettime(CLOCK_MONOTONIC, &timeSpec);
505         if (timeSpec.tv_sec >= static_cast<time_t>(
506             (startTimeSec + kPlayTimeSeconds))) {
507             // stop reading samples and drain the output buffers
508             sawAudioInputEos = sawVideoInputEos = true;
509             sawAudioOutputEos = true; // ignore audio
510         }
511     }
512 
513     if (audioCodec) {
514         AMediaCodec_stop(audioCodec);
515         AMediaCodec_delete(audioCodec);
516     }
517     if (videoCodec) {
518         AMediaCodec_stop(videoCodec);
519         AMediaCodec_delete(videoCodec);
520     }
521 
522     AMediaCrypto_delete(crypto);
523     ANativeWindow_release(window);
524     return JNI_TRUE;
525 }
526 
listener(AMediaDrm *,const AMediaDrmSessionId *,AMediaDrmEventType eventType,int,const uint8_t *,size_t)527 static void listener(
528     AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
529     AMediaDrmEventType eventType,
530     int /*extra*/, const uint8_t* /*data*/, size_t /*dataSize*/) {
531 
532     switch (eventType) {
533         case EVENT_PROVISION_REQUIRED:
534             ALOGD("EVENT_PROVISION_REQUIRED received");
535             break;
536         case EVENT_KEY_REQUIRED:
537             ALOGD("EVENT_KEY_REQUIRED received");
538             break;
539         case EVENT_KEY_EXPIRED:
540             ALOGD("EVENT_KEY_EXPIRED received");
541             break;
542         case EVENT_VENDOR_DEFINED:
543             gGotVendorDefinedEvent = true;
544             ALOGD("EVENT_VENDOR_DEFINED received");
545             break;
546         case EVENT_SESSION_RECLAIMED:
547             ALOGD("EVENT_SESSION_RECLAIMED received");
548             break;
549         default:
550             ALOGD("Unknown event received");
551             break;
552     }
553 }
554 
onExpirationUpdateListener(AMediaDrm *,const AMediaDrmSessionId *,int64_t expiryTimeInMS)555 static void onExpirationUpdateListener(
556     AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
557     int64_t expiryTimeInMS) {
558 
559     if (expiryTimeInMS == 100) {
560         ALOGD("Updates new expiration time to %" PRId64 " ms", expiryTimeInMS);
561         gListenerGotValidExpiryTime = true;
562     } else {
563         ALOGE("Expects 100 ms for expiry time, received: %" PRId64 " ms", expiryTimeInMS);
564         gListenerGotValidExpiryTime = false;
565     }
566 }
567 
onKeysChangeListener(AMediaDrm *,const AMediaDrmSessionId *,const AMediaDrmKeyStatus * keysStatus,size_t numKeys,bool hasNewUsableKey)568 static void onKeysChangeListener(
569     AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
570     const AMediaDrmKeyStatus* keysStatus, size_t numKeys, bool hasNewUsableKey) {
571 
572     gOnKeyChangeListenerOK = false;
573     if (numKeys != 3) {
574         ALOGE("Expects 3 keys, received %zd keys", numKeys);
575         return;
576     }
577 
578     if (!hasNewUsableKey) {
579         ALOGE("Expects hasNewUsableKey to be true");
580         return;
581     }
582 
583     ALOGD("Number of keys changed=%zd", numKeys);
584     AMediaDrmKeyStatus keyStatus;
585     for (size_t i = 0; i < numKeys; ++i) {
586         keyStatus.keyId.ptr = keysStatus[i].keyId.ptr;
587         keyStatus.keyId.length = keysStatus[i].keyId.length;
588         keyStatus.keyType = keysStatus[i].keyType;
589 
590         ALOGD("key[%zd]: key: %0x, %0x, %0x", i, keyStatus.keyId.ptr[0], keyStatus.keyId.ptr[1],
591                 keyStatus.keyId.ptr[2]);
592         ALOGD("key[%zd]: key type=%d", i, keyStatus.keyType);
593     }
594     gOnKeyChangeListenerOK = true;
595 }
596 
acquireLicense(JNIEnv * env,const AMediaObjects & aMediaObjects,const AMediaDrmSessionId & sessionId,AMediaDrmKeyType keyType)597 static void acquireLicense(
598     JNIEnv* env, const AMediaObjects& aMediaObjects, const AMediaDrmSessionId& sessionId,
599     AMediaDrmKeyType keyType) {
600     // Pointer to keyRequest memory, which remains until the next
601     // AMediaDrm_getKeyRequest call or until the drm object is released.
602     const uint8_t* keyRequest;
603     size_t keyRequestSize = 0;
604     std::string errorMessage;
605 
606     // The server recognizes "video/mp4" but not "video/avc".
607     media_status_t status = AMediaDrm_getKeyRequest(aMediaObjects.getDrm(),
608             &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
609             "video/mp4" /*mimeType*/, keyType,
610             NULL, 0, &keyRequest, &keyRequestSize);
611     if (status != AMEDIA_OK) {
612         errorMessage.assign("getKeyRequest failed, error = %d");
613         goto errorOut;
614     }
615 
616     if (kKeyRequestSize != keyRequestSize) {
617         ALOGE("Invalid keyRequestSize %zd", kKeyRequestSize);
618         errorMessage.assign("Invalid key request size, error = %d");
619         status = AMEDIA_DRM_NEED_KEY;
620         goto errorOut;
621     }
622 
623     if (memcmp(kKeyRequestData, keyRequest, kKeyRequestSize) != 0) {
624         errorMessage.assign("Invalid key request data is returned, error = %d");
625         status = AMEDIA_DRM_NEED_KEY;
626         goto errorOut;
627     }
628 
629     AMediaDrmKeySetId keySetId;
630     gGotVendorDefinedEvent = false;
631     gListenerGotValidExpiryTime = false;
632     gOnKeyChangeListenerOK = false;
633     status = AMediaDrm_provideKeyResponse(aMediaObjects.getDrm(), &sessionId,
634             reinterpret_cast<const uint8_t*>(kResponse),
635             sizeof(kResponse), &keySetId);
636     if (status == AMEDIA_OK) {
637         return;  // success
638     }
639 
640     errorMessage.assign("provideKeyResponse failed, error = %d");
641 
642 errorOut:
643     AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
644     jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), status);
645 }
646 
Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(JNIEnv * env,jclass,jbyteArray uuid,jobject playbackParams)647 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(
648     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
649     if (NULL == uuid || NULL == playbackParams) {
650         jniThrowException(env, "java/lang/NullPointerException",
651                 "null uuid or null playback parameters");
652         return JNI_FALSE;
653     }
654 
655     Uuid juuid = jbyteArrayToUuid(env, uuid);
656     if (!isUuidSizeValid(juuid)) {
657         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
658                 "invalid UUID size, expected %u bytes", kUuidSize);
659         return JNI_FALSE;
660     }
661 
662     PlaybackParams params;
663     initPlaybackParams(env, playbackParams, params);
664 
665     AMediaObjects aMediaObjects;
666     media_status_t status = AMEDIA_OK;
667     aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
668     if (NULL == aMediaObjects.getDrm()) {
669         jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
670         return JNI_FALSE;
671     }
672 
673     status = AMediaDrm_setOnEventListener(aMediaObjects.getDrm(), listener);
674     if (status != AMEDIA_OK) {
675         jniThrowException(env, "java/lang/RuntimeException",
676                 "setOnEventListener failed");
677         return JNI_FALSE;
678     }
679 
680     status = AMediaDrm_setOnExpirationUpdateListener(aMediaObjects.getDrm(),
681             onExpirationUpdateListener);
682     if (status != AMEDIA_OK) {
683         jniThrowException(env, "java/lang/RuntimeException",
684                 "setOnExpirationUpdateListener failed");
685         return JNI_FALSE;
686     }
687 
688     status = AMediaDrm_setOnKeysChangeListener(aMediaObjects.getDrm(),
689             onKeysChangeListener);
690     if (status != AMEDIA_OK) {
691         jniThrowException(env, "java/lang/RuntimeException",
692                 "setOnKeysChangeListener failed");
693         return JNI_FALSE;
694     }
695 
696     aMediaObjects.setAudioExtractor(AMediaExtractor_new());
697     const char* url = env->GetStringUTFChars(params.audioUrl, 0);
698     if (url) {
699         status = AMediaExtractor_setDataSource(
700             aMediaObjects.getAudioExtractor(), url);
701         env->ReleaseStringUTFChars(params.audioUrl, url);
702 
703         if (status != AMEDIA_OK) {
704             jniThrowExceptionFmt(env, "java/lang/RuntimeException",
705                     "set audio data source error=%d", status);
706             return JNI_FALSE;
707         }
708     }
709 
710     aMediaObjects.setVideoExtractor(AMediaExtractor_new());
711     url = env->GetStringUTFChars(params.videoUrl, 0);
712     if (url) {
713         status = AMediaExtractor_setDataSource(
714             aMediaObjects.getVideoExtractor(), url);
715         env->ReleaseStringUTFChars(params.videoUrl, url);
716 
717         if (status != AMEDIA_OK) {
718             jniThrowExceptionFmt(env, "java/lang/RuntimeException",
719                     "set video data source error=%d", status);
720             return JNI_FALSE;
721         }
722     }
723 
724     AMediaDrmSessionId sessionId;
725     status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
726     if (status != AMEDIA_OK) {
727         jniThrowException(env, "java/lang/RuntimeException",
728                 "openSession failed");
729         return JNI_FALSE;
730     }
731 
732     acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
733 
734     // Checks if the event listener has received the expected event sent by
735     // provideKeyResponse. This is for testing AMediaDrm_setOnEventListener().
736     const char *utf8_outValue = NULL;
737     status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
738             "listenerTestSupport", &utf8_outValue);
739     if (status == AMEDIA_OK && NULL != utf8_outValue) {
740         std::string eventType(utf8_outValue);
741         if (eventType.compare("true") == 0) {
742             int count = 0;
743             while ((!gGotVendorDefinedEvent ||
744                     !gListenerGotValidExpiryTime ||
745                     !gOnKeyChangeListenerOK) && count++ < 5) {
746                // Prevents race condition when the event arrives late
747                usleep(10000);
748             }
749 
750             if (!gGotVendorDefinedEvent) {
751                 ALOGE("Event listener did not receive the expected event.");
752                 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
753                         "Event listener did not receive the expected event.");
754                 AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
755                 return JNI_FALSE;
756            }
757 
758           // Checks if onExpirationUpdateListener received the correct expiry time.
759            if (!gListenerGotValidExpiryTime) {
760                jniThrowExceptionFmt(env, "java/lang/RuntimeException",
761                        "onExpirationUpdateListener received incorrect expiry time.");
762                AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
763                return JNI_FALSE;
764            }
765 
766           // Checks if onKeysChangeListener succeeded.
767           if (!gOnKeyChangeListenerOK) {
768               jniThrowExceptionFmt(env, "java/lang/RuntimeException",
769                       "onKeysChangeListener failed");
770               AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
771               return JNI_FALSE;
772           }
773         }
774     }
775 
776     playContent(env, aMediaObjects, params, sessionId, juuid);
777 
778     status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
779     if (status != AMEDIA_OK) {
780         jniThrowException(env, "java/lang/RuntimeException",
781                 "closeSession failed");
782         return JNI_FALSE;
783     }
784     return JNI_TRUE;
785 }
786 
Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(JNIEnv * env,jclass,jbyteArray uuid)787 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(
788     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
789 
790     if (NULL == uuid) {
791         jniThrowException(env, "java/lang/NullPointerException", "null uuid");
792         return JNI_FALSE;
793     }
794 
795     Uuid juuid = jbyteArrayToUuid(env, uuid);
796     if (!isUuidSizeValid(juuid)) {
797         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
798                 "invalid UUID size, expected %u bytes", kUuidSize);
799         return JNI_FALSE;
800     }
801 
802     AMediaObjects aMediaObjects;
803     media_status_t status = AMEDIA_OK;
804     aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
805     if (NULL == aMediaObjects.getDrm()) {
806         jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
807         return JNI_FALSE;
808     }
809 
810     AMediaDrmSessionId sessionId;
811     status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
812     if (status != AMEDIA_OK) {
813         jniThrowException(env, "java/lang/RuntimeException",
814                 "openSession failed");
815         return JNI_FALSE;
816     }
817 
818     size_t numPairs = 3;
819     AMediaDrmKeyValue keyStatus[numPairs];
820 
821     // Test default key status, should return zero key status
822     status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
823     if (status != AMEDIA_OK) {
824         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
825                 "AMediaDrm_queryKeyStatus failed, error = %d", status);
826         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
827         return JNI_FALSE;
828     }
829 
830     if (numPairs != 0) {
831         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
832                 "AMediaDrm_queryKeyStatus failed, no policy should be defined");
833         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
834         return JNI_FALSE;
835     }
836 
837     acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
838 
839     // Test short buffer
840     numPairs = 2;
841     status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
842     if (status != AMEDIA_DRM_SHORT_BUFFER) {
843         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
844                 "AMediaDrm_queryKeyStatus should return AMEDIA_DRM_SHORT_BUFFER, error = %d",
845                         status);
846         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
847         return JNI_FALSE;
848     }
849 
850     // Test valid key status
851     numPairs = 3;
852     status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
853     if (status != AMEDIA_OK) {
854         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
855                 "AMediaDrm_queryKeyStatus failed, error = %d", status);
856         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
857         return JNI_FALSE;
858     }
859 
860     for (size_t i = 0; i < numPairs; ++i) {
861         ALOGI("AMediaDrm_queryKeyStatus: key=%s, value=%s", keyStatus[i].mKey, keyStatus[i].mValue);
862     }
863 
864     if (numPairs != 3) {
865         jniThrowExceptionFmt(env, "java/lang/RuntimeException",
866                 "AMediaDrm_queryKeyStatus returns %zd key status, expecting 3", numPairs);
867         AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
868         return JNI_FALSE;
869     }
870 
871     status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
872     if (status != AMEDIA_OK) {
873         jniThrowException(env, "java/lang/RuntimeException",
874                 "closeSession failed");
875         return JNI_FALSE;
876     }
877     return JNI_TRUE;
878 }
879 
Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative(JNIEnv * env,jclass,jbyteArray uuid)880 extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative(
881     JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
882 
883     if (NULL == uuid) {
884         jniThrowException(env, "java/lang/NullPointerException", "null uuid");
885         return JNI_FALSE;
886     }
887 
888     Uuid juuid = jbyteArrayToUuid(env, uuid);
889     if (!isUuidSizeValid(juuid)) {
890         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
891                 "invalid UUID size, expected %u bytes", kUuidSize);
892         return JNI_FALSE;
893     }
894 
895     AMediaObjects aMediaObjects;
896     media_status_t status = AMEDIA_OK;
897     aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
898     if (NULL == aMediaObjects.getDrm()) {
899         jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
900         return JNI_FALSE;
901     }
902 
903     // Stores duplicates of session id.
904     std::vector<std::vector<uint8_t> > sids;
905 
906     std::list<AMediaDrmSessionId> sessionIds;
907     AMediaDrmSessionId sessionId;
908     for (int i = 0; i < 5; ++i) {
909         status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
910         if (status != AMEDIA_OK) {
911             jniThrowException(env, "java/lang/RuntimeException", "openSession failed");
912             return JNI_FALSE;
913         }
914 
915         // Allocates a new pointer to duplicate the session id returned by
916         // AMediaDrm_openSession. These new pointers will be passed to
917         // AMediaDrm_closeSession, which verifies that the ndk
918         // can find the session id even if the pointer has changed.
919         sids.push_back(std::vector<uint8_t>(sessionId.length));
920         memcpy(sids.at(i).data(), sessionId.ptr, sessionId.length);
921         sessionId.ptr = static_cast<uint8_t *>(sids.at(i).data());
922         sessionIds.push_back(sessionId);
923     }
924 
925     for (auto sessionId : sessionIds) {
926         status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
927         if (status != AMEDIA_OK) {
928             jniThrowException(env, "java/lang/RuntimeException", "closeSession failed");
929             return JNI_FALSE;
930         }
931     }
932 
933     return JNI_TRUE;
934 }
935 
936 static JNINativeMethod gMethods[] = {
937     { "isCryptoSchemeSupportedNative", "([B)Z",
938             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
939 
940     { "testClearKeyPlaybackNative",
941             "([BLandroid/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
942             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative },
943 
944     { "testGetPropertyStringNative",
945             "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
946             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative },
947 
948     { "testPropertyByteArrayNative",
949             "([B)Z",
950             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative },
951 
952     { "testPsshNative", "([BLjava/lang/String;)Z",
953             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative },
954 
955     { "testQueryKeyStatusNative", "([B)Z",
956             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative },
957 
958     { "testFindSessionIdNative", "([B)Z",
959             (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative },
960 };
961 
register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv * env)962 int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
963     jint result = JNI_ERR;
964     jclass testClass =
965         env->FindClass("android/media/cts/NativeMediaDrmClearkeyTest");
966     if (testClass) {
967         jclass playbackParamsClass = env->FindClass(
968             "android/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams");
969         if (playbackParamsClass) {
970             jclass surfaceClass =
971                 env->FindClass("android/view/Surface");
972             if (surfaceClass) {
973                 gFieldIds.surface = env->GetFieldID(playbackParamsClass,
974                         "surface", "Landroid/view/Surface;");
975             } else {
976                 gFieldIds.surface = NULL;
977             }
978             gFieldIds.mimeType = env->GetFieldID(playbackParamsClass,
979                     "mimeType", "Ljava/lang/String;");
980             gFieldIds.audioUrl = env->GetFieldID(playbackParamsClass,
981                     "audioUrl", "Ljava/lang/String;");
982             gFieldIds.videoUrl = env->GetFieldID(playbackParamsClass,
983                     "videoUrl", "Ljava/lang/String;");
984         } else {
985             ALOGE("PlaybackParams class not found");
986         }
987 
988     } else {
989         ALOGE("NativeMediaDrmClearkeyTest class not found");
990     }
991 
992     result = env->RegisterNatives(testClass, gMethods,
993             sizeof(gMethods) / sizeof(JNINativeMethod));
994     return result;
995 }
996