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