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