1 /*
2 **
3 ** Copyright 2007, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 ** http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17
18 //#define LOG_NDEBUG 0
19 #define LOG_TAG "MediaScannerJNI"
20 #include <utils/Log.h>
21 #include <utils/threads.h>
22 #include <media/mediascanner.h>
23 #include <media/stagefright/StagefrightMediaScanner.h>
24 #include <private/media/VideoFrame.h>
25
26 #include "jni.h"
27 #include "JNIHelp.h"
28 #include "android_runtime/AndroidRuntime.h"
29 #include "android_runtime/Log.h"
30
31 using namespace android;
32
33
34 static const char* const kClassMediaScannerClient =
35 "android/media/MediaScannerClient";
36
37 static const char* const kClassMediaScanner =
38 "android/media/MediaScanner";
39
40 static const char* const kRunTimeException =
41 "java/lang/RuntimeException";
42
43 static const char* const kIllegalArgumentException =
44 "java/lang/IllegalArgumentException";
45
46 struct fields_t {
47 jfieldID context;
48 };
49 static fields_t fields;
50
checkAndClearExceptionFromCallback(JNIEnv * env,const char * methodName)51 static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
52 if (env->ExceptionCheck()) {
53 ALOGE("An exception was thrown by callback '%s'.", methodName);
54 LOGE_EX(env);
55 env->ExceptionClear();
56 return UNKNOWN_ERROR;
57 }
58 return OK;
59 }
60
61 // stolen from dalvik/vm/checkJni.cpp
isValidUtf8(const char * bytes)62 static bool isValidUtf8(const char* bytes) {
63 while (*bytes != '\0') {
64 unsigned char utf8 = *(bytes++);
65 // Switch on the high four bits.
66 switch (utf8 >> 4) {
67 case 0x00:
68 case 0x01:
69 case 0x02:
70 case 0x03:
71 case 0x04:
72 case 0x05:
73 case 0x06:
74 case 0x07:
75 // Bit pattern 0xxx. No need for any extra bytes.
76 break;
77 case 0x08:
78 case 0x09:
79 case 0x0a:
80 case 0x0b:
81 case 0x0f:
82 /*
83 * Bit pattern 10xx or 1111, which are illegal start bytes.
84 * Note: 1111 is valid for normal UTF-8, but not the
85 * modified UTF-8 used here.
86 */
87 return false;
88 case 0x0e:
89 // Bit pattern 1110, so there are two additional bytes.
90 utf8 = *(bytes++);
91 if ((utf8 & 0xc0) != 0x80) {
92 return false;
93 }
94 // Fall through to take care of the final byte.
95 case 0x0c:
96 case 0x0d:
97 // Bit pattern 110x, so there is one additional byte.
98 utf8 = *(bytes++);
99 if ((utf8 & 0xc0) != 0x80) {
100 return false;
101 }
102 break;
103 }
104 }
105 return true;
106 }
107
108 class MyMediaScannerClient : public MediaScannerClient
109 {
110 public:
MyMediaScannerClient(JNIEnv * env,jobject client)111 MyMediaScannerClient(JNIEnv *env, jobject client)
112 : mEnv(env),
113 mClient(env->NewGlobalRef(client)),
114 mScanFileMethodID(0),
115 mHandleStringTagMethodID(0),
116 mSetMimeTypeMethodID(0)
117 {
118 ALOGV("MyMediaScannerClient constructor");
119 jclass mediaScannerClientInterface =
120 env->FindClass(kClassMediaScannerClient);
121
122 if (mediaScannerClientInterface == NULL) {
123 ALOGE("Class %s not found", kClassMediaScannerClient);
124 } else {
125 mScanFileMethodID = env->GetMethodID(
126 mediaScannerClientInterface,
127 "scanFile",
128 "(Ljava/lang/String;JJZZ)V");
129
130 mHandleStringTagMethodID = env->GetMethodID(
131 mediaScannerClientInterface,
132 "handleStringTag",
133 "(Ljava/lang/String;Ljava/lang/String;)V");
134
135 mSetMimeTypeMethodID = env->GetMethodID(
136 mediaScannerClientInterface,
137 "setMimeType",
138 "(Ljava/lang/String;)V");
139 }
140 }
141
~MyMediaScannerClient()142 virtual ~MyMediaScannerClient()
143 {
144 ALOGV("MyMediaScannerClient destructor");
145 mEnv->DeleteGlobalRef(mClient);
146 }
147
scanFile(const char * path,long long lastModified,long long fileSize,bool isDirectory,bool noMedia)148 virtual status_t scanFile(const char* path, long long lastModified,
149 long long fileSize, bool isDirectory, bool noMedia)
150 {
151 ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
152 path, lastModified, fileSize, isDirectory);
153
154 jstring pathStr;
155 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
156 mEnv->ExceptionClear();
157 return NO_MEMORY;
158 }
159
160 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
161 fileSize, isDirectory, noMedia);
162
163 mEnv->DeleteLocalRef(pathStr);
164 return checkAndClearExceptionFromCallback(mEnv, "scanFile");
165 }
166
handleStringTag(const char * name,const char * value)167 virtual status_t handleStringTag(const char* name, const char* value)
168 {
169 ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
170 jstring nameStr, valueStr;
171 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
172 mEnv->ExceptionClear();
173 return NO_MEMORY;
174 }
175 char *cleaned = NULL;
176 if (!isValidUtf8(value)) {
177 cleaned = strdup(value);
178 char *chp = cleaned;
179 char ch;
180 while ((ch = *chp)) {
181 if (ch & 0x80) {
182 *chp = '?';
183 }
184 chp++;
185 }
186 value = cleaned;
187 }
188 valueStr = mEnv->NewStringUTF(value);
189 free(cleaned);
190 if (valueStr == NULL) {
191 mEnv->DeleteLocalRef(nameStr);
192 mEnv->ExceptionClear();
193 return NO_MEMORY;
194 }
195
196 mEnv->CallVoidMethod(
197 mClient, mHandleStringTagMethodID, nameStr, valueStr);
198
199 mEnv->DeleteLocalRef(nameStr);
200 mEnv->DeleteLocalRef(valueStr);
201 return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
202 }
203
setMimeType(const char * mimeType)204 virtual status_t setMimeType(const char* mimeType)
205 {
206 ALOGV("setMimeType: %s", mimeType);
207 jstring mimeTypeStr;
208 if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
209 mEnv->ExceptionClear();
210 return NO_MEMORY;
211 }
212
213 mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
214
215 mEnv->DeleteLocalRef(mimeTypeStr);
216 return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
217 }
218
219 private:
220 JNIEnv *mEnv;
221 jobject mClient;
222 jmethodID mScanFileMethodID;
223 jmethodID mHandleStringTagMethodID;
224 jmethodID mSetMimeTypeMethodID;
225 };
226
227
getNativeScanner_l(JNIEnv * env,jobject thiz)228 static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
229 {
230 return (MediaScanner *) env->GetLongField(thiz, fields.context);
231 }
232
setNativeScanner_l(JNIEnv * env,jobject thiz,MediaScanner * s)233 static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
234 {
235 env->SetLongField(thiz, fields.context, (jlong)s);
236 }
237
238 static void
android_media_MediaScanner_processDirectory(JNIEnv * env,jobject thiz,jstring path,jobject client)239 android_media_MediaScanner_processDirectory(
240 JNIEnv *env, jobject thiz, jstring path, jobject client)
241 {
242 ALOGV("processDirectory");
243 MediaScanner *mp = getNativeScanner_l(env, thiz);
244 if (mp == NULL) {
245 jniThrowException(env, kRunTimeException, "No scanner available");
246 return;
247 }
248
249 if (path == NULL) {
250 jniThrowException(env, kIllegalArgumentException, NULL);
251 return;
252 }
253
254 const char *pathStr = env->GetStringUTFChars(path, NULL);
255 if (pathStr == NULL) { // Out of memory
256 return;
257 }
258
259 MyMediaScannerClient myClient(env, client);
260 MediaScanResult result = mp->processDirectory(pathStr, myClient);
261 if (result == MEDIA_SCAN_RESULT_ERROR) {
262 ALOGE("An error occurred while scanning directory '%s'.", pathStr);
263 }
264 env->ReleaseStringUTFChars(path, pathStr);
265 }
266
267 static void
android_media_MediaScanner_processFile(JNIEnv * env,jobject thiz,jstring path,jstring mimeType,jobject client)268 android_media_MediaScanner_processFile(
269 JNIEnv *env, jobject thiz, jstring path,
270 jstring mimeType, jobject client)
271 {
272 ALOGV("processFile");
273
274 // Lock already hold by processDirectory
275 MediaScanner *mp = getNativeScanner_l(env, thiz);
276 if (mp == NULL) {
277 jniThrowException(env, kRunTimeException, "No scanner available");
278 return;
279 }
280
281 if (path == NULL) {
282 jniThrowException(env, kIllegalArgumentException, NULL);
283 return;
284 }
285
286 const char *pathStr = env->GetStringUTFChars(path, NULL);
287 if (pathStr == NULL) { // Out of memory
288 return;
289 }
290
291 const char *mimeTypeStr =
292 (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
293 if (mimeType && mimeTypeStr == NULL) { // Out of memory
294 // ReleaseStringUTFChars can be called with an exception pending.
295 env->ReleaseStringUTFChars(path, pathStr);
296 return;
297 }
298
299 MyMediaScannerClient myClient(env, client);
300 MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
301 if (result == MEDIA_SCAN_RESULT_ERROR) {
302 ALOGE("An error occurred while scanning file '%s'.", pathStr);
303 }
304 env->ReleaseStringUTFChars(path, pathStr);
305 if (mimeType) {
306 env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
307 }
308 }
309
310 static void
android_media_MediaScanner_setLocale(JNIEnv * env,jobject thiz,jstring locale)311 android_media_MediaScanner_setLocale(
312 JNIEnv *env, jobject thiz, jstring locale)
313 {
314 ALOGV("setLocale");
315 MediaScanner *mp = getNativeScanner_l(env, thiz);
316 if (mp == NULL) {
317 jniThrowException(env, kRunTimeException, "No scanner available");
318 return;
319 }
320
321 if (locale == NULL) {
322 jniThrowException(env, kIllegalArgumentException, NULL);
323 return;
324 }
325 const char *localeStr = env->GetStringUTFChars(locale, NULL);
326 if (localeStr == NULL) { // Out of memory
327 return;
328 }
329 mp->setLocale(localeStr);
330
331 env->ReleaseStringUTFChars(locale, localeStr);
332 }
333
334 static jbyteArray
android_media_MediaScanner_extractAlbumArt(JNIEnv * env,jobject thiz,jobject fileDescriptor)335 android_media_MediaScanner_extractAlbumArt(
336 JNIEnv *env, jobject thiz, jobject fileDescriptor)
337 {
338 ALOGV("extractAlbumArt");
339 MediaScanner *mp = getNativeScanner_l(env, thiz);
340 if (mp == NULL) {
341 jniThrowException(env, kRunTimeException, "No scanner available");
342 return NULL;
343 }
344
345 if (fileDescriptor == NULL) {
346 jniThrowException(env, kIllegalArgumentException, NULL);
347 return NULL;
348 }
349
350 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
351 MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
352 if (mediaAlbumArt == NULL) {
353 return NULL;
354 }
355
356 jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
357 if (array != NULL) {
358 const jbyte* data =
359 reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
360 env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
361 }
362
363 free(mediaAlbumArt);
364 // if NewByteArray() returned NULL, an out-of-memory
365 // exception will have been raised. I just want to
366 // return null in that case.
367 env->ExceptionClear();
368 return array;
369 }
370
371 // This function gets a field ID, which in turn causes class initialization.
372 // It is called from a static block in MediaScanner, which won't run until the
373 // first time an instance of this class is used.
374 static void
android_media_MediaScanner_native_init(JNIEnv * env)375 android_media_MediaScanner_native_init(JNIEnv *env)
376 {
377 ALOGV("native_init");
378 jclass clazz = env->FindClass(kClassMediaScanner);
379 if (clazz == NULL) {
380 return;
381 }
382
383 fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
384 if (fields.context == NULL) {
385 return;
386 }
387 }
388
389 static void
android_media_MediaScanner_native_setup(JNIEnv * env,jobject thiz)390 android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
391 {
392 ALOGV("native_setup");
393 MediaScanner *mp = new StagefrightMediaScanner;
394
395 if (mp == NULL) {
396 jniThrowException(env, kRunTimeException, "Out of memory");
397 return;
398 }
399
400 env->SetLongField(thiz, fields.context, (jlong)mp);
401 }
402
403 static void
android_media_MediaScanner_native_finalize(JNIEnv * env,jobject thiz)404 android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
405 {
406 ALOGV("native_finalize");
407 MediaScanner *mp = getNativeScanner_l(env, thiz);
408 if (mp == 0) {
409 return;
410 }
411 delete mp;
412 setNativeScanner_l(env, thiz, 0);
413 }
414
415 static const JNINativeMethod gMethods[] = {
416 {
417 "processDirectory",
418 "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
419 (void *)android_media_MediaScanner_processDirectory
420 },
421
422 {
423 "processFile",
424 "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
425 (void *)android_media_MediaScanner_processFile
426 },
427
428 {
429 "setLocale",
430 "(Ljava/lang/String;)V",
431 (void *)android_media_MediaScanner_setLocale
432 },
433
434 {
435 "extractAlbumArt",
436 "(Ljava/io/FileDescriptor;)[B",
437 (void *)android_media_MediaScanner_extractAlbumArt
438 },
439
440 {
441 "native_init",
442 "()V",
443 (void *)android_media_MediaScanner_native_init
444 },
445
446 {
447 "native_setup",
448 "()V",
449 (void *)android_media_MediaScanner_native_setup
450 },
451
452 {
453 "native_finalize",
454 "()V",
455 (void *)android_media_MediaScanner_native_finalize
456 },
457 };
458
459 // This function only registers the native methods, and is called from
460 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaScanner(JNIEnv * env)461 int register_android_media_MediaScanner(JNIEnv *env)
462 {
463 return AndroidRuntime::registerNativeMethods(env,
464 kClassMediaScanner, gMethods, NELEM(gMethods));
465 }
466