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