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 ¶ms) {
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