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 &params) {
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