• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <nativehelper/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 jboolean
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 false;
279     }
280 
281     if (path == NULL) {
282         jniThrowException(env, kIllegalArgumentException, NULL);
283         return false;
284     }
285 
286     const char *pathStr = env->GetStringUTFChars(path, NULL);
287     if (pathStr == NULL) {  // Out of memory
288         return false;
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 false;
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     return result != MEDIA_SCAN_RESULT_ERROR;
309 }
310 
311 static void
android_media_MediaScanner_setLocale(JNIEnv * env,jobject thiz,jstring locale)312 android_media_MediaScanner_setLocale(
313         JNIEnv *env, jobject thiz, jstring locale)
314 {
315     ALOGV("setLocale");
316     MediaScanner *mp = getNativeScanner_l(env, thiz);
317     if (mp == NULL) {
318         jniThrowException(env, kRunTimeException, "No scanner available");
319         return;
320     }
321 
322     if (locale == NULL) {
323         jniThrowException(env, kIllegalArgumentException, NULL);
324         return;
325     }
326     const char *localeStr = env->GetStringUTFChars(locale, NULL);
327     if (localeStr == NULL) {  // Out of memory
328         return;
329     }
330     mp->setLocale(localeStr);
331 
332     env->ReleaseStringUTFChars(locale, localeStr);
333 }
334 
335 static jbyteArray
android_media_MediaScanner_extractAlbumArt(JNIEnv * env,jobject thiz,jobject fileDescriptor)336 android_media_MediaScanner_extractAlbumArt(
337         JNIEnv *env, jobject thiz, jobject fileDescriptor)
338 {
339     ALOGV("extractAlbumArt");
340     MediaScanner *mp = getNativeScanner_l(env, thiz);
341     if (mp == NULL) {
342         jniThrowException(env, kRunTimeException, "No scanner available");
343         return NULL;
344     }
345 
346     if (fileDescriptor == NULL) {
347         jniThrowException(env, kIllegalArgumentException, NULL);
348         return NULL;
349     }
350 
351     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
352     MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
353     if (mediaAlbumArt == NULL) {
354         return NULL;
355     }
356 
357     jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
358     if (array != NULL) {
359         const jbyte* data =
360                 reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
361         env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
362     }
363 
364     free(mediaAlbumArt);
365     // if NewByteArray() returned NULL, an out-of-memory
366     // exception will have been raised. I just want to
367     // return null in that case.
368     env->ExceptionClear();
369     return array;
370 }
371 
372 // This function gets a field ID, which in turn causes class initialization.
373 // It is called from a static block in MediaScanner, which won't run until the
374 // first time an instance of this class is used.
375 static void
android_media_MediaScanner_native_init(JNIEnv * env)376 android_media_MediaScanner_native_init(JNIEnv *env)
377 {
378     ALOGV("native_init");
379     jclass clazz = env->FindClass(kClassMediaScanner);
380     if (clazz == NULL) {
381         return;
382     }
383 
384     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
385     if (fields.context == NULL) {
386         return;
387     }
388 }
389 
390 static void
android_media_MediaScanner_native_setup(JNIEnv * env,jobject thiz)391 android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
392 {
393     ALOGV("native_setup");
394     MediaScanner *mp = new StagefrightMediaScanner;
395 
396     if (mp == NULL) {
397         jniThrowException(env, kRunTimeException, "Out of memory");
398         return;
399     }
400 
401     env->SetLongField(thiz, fields.context, (jlong)mp);
402 }
403 
404 static void
android_media_MediaScanner_native_finalize(JNIEnv * env,jobject thiz)405 android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
406 {
407     ALOGV("native_finalize");
408     MediaScanner *mp = getNativeScanner_l(env, thiz);
409     if (mp == 0) {
410         return;
411     }
412     delete mp;
413     setNativeScanner_l(env, thiz, 0);
414 }
415 
416 static const JNINativeMethod gMethods[] = {
417     {
418         "processDirectory",
419         "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
420         (void *)android_media_MediaScanner_processDirectory
421     },
422 
423     {
424         "processFile",
425         "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
426         (void *)android_media_MediaScanner_processFile
427     },
428 
429     {
430         "setLocale",
431         "(Ljava/lang/String;)V",
432         (void *)android_media_MediaScanner_setLocale
433     },
434 
435     {
436         "extractAlbumArt",
437         "(Ljava/io/FileDescriptor;)[B",
438         (void *)android_media_MediaScanner_extractAlbumArt
439     },
440 
441     {
442         "native_init",
443         "()V",
444         (void *)android_media_MediaScanner_native_init
445     },
446 
447     {
448         "native_setup",
449         "()V",
450         (void *)android_media_MediaScanner_native_setup
451     },
452 
453     {
454         "native_finalize",
455         "()V",
456         (void *)android_media_MediaScanner_native_finalize
457     },
458 };
459 
460 // This function only registers the native methods, and is called from
461 // JNI_OnLoad in android_media_MediaPlayer.cpp
register_android_media_MediaScanner(JNIEnv * env)462 int register_android_media_MediaScanner(JNIEnv *env)
463 {
464     return AndroidRuntime::registerNativeMethods(env,
465                 kClassMediaScanner, gMethods, NELEM(gMethods));
466 }
467