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