android_media_MediaScanner.cpp revision 3762c311729fe9f3af085c14c5c1fb471d994c03
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
25#include "jni.h"
26#include "JNIHelp.h"
27#include "android_runtime/AndroidRuntime.h"
28
29using namespace android;
30
31
32static const char* const kClassMediaScannerClient =
33        "android/media/MediaScannerClient";
34
35static const char* const kClassMediaScanner =
36        "android/media/MediaScanner";
37
38static const char* const kRunTimeException =
39        "java/lang/RuntimeException";
40
41static const char* const kIllegalArgumentException =
42        "java/lang/IllegalArgumentException";
43
44struct fields_t {
45    jfieldID    context;
46};
47static fields_t fields;
48
49static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
50    if (env->ExceptionCheck()) {
51        ALOGE("An exception was thrown by callback '%s'.", methodName);
52        LOGE_EX(env);
53        env->ExceptionClear();
54        return UNKNOWN_ERROR;
55    }
56    return OK;
57}
58
59// stolen from dalvik/vm/checkJni.cpp
60static bool isValidUtf8(const char* bytes) {
61    while (*bytes != '\0') {
62        unsigned char utf8 = *(bytes++);
63        // Switch on the high four bits.
64        switch (utf8 >> 4) {
65        case 0x00:
66        case 0x01:
67        case 0x02:
68        case 0x03:
69        case 0x04:
70        case 0x05:
71        case 0x06:
72        case 0x07:
73            // Bit pattern 0xxx. No need for any extra bytes.
74            break;
75        case 0x08:
76        case 0x09:
77        case 0x0a:
78        case 0x0b:
79        case 0x0f:
80            /*
81             * Bit pattern 10xx or 1111, which are illegal start bytes.
82             * Note: 1111 is valid for normal UTF-8, but not the
83             * modified UTF-8 used here.
84             */
85            return false;
86        case 0x0e:
87            // Bit pattern 1110, so there are two additional bytes.
88            utf8 = *(bytes++);
89            if ((utf8 & 0xc0) != 0x80) {
90                return false;
91            }
92            // Fall through to take care of the final byte.
93        case 0x0c:
94        case 0x0d:
95            // Bit pattern 110x, so there is one additional byte.
96            utf8 = *(bytes++);
97            if ((utf8 & 0xc0) != 0x80) {
98                return false;
99            }
100            break;
101        }
102    }
103    return true;
104}
105
106class MyMediaScannerClient : public MediaScannerClient
107{
108public:
109    MyMediaScannerClient(JNIEnv *env, jobject client)
110        :   mEnv(env),
111            mClient(env->NewGlobalRef(client)),
112            mScanFileMethodID(0),
113            mHandleStringTagMethodID(0),
114            mSetMimeTypeMethodID(0)
115    {
116        ALOGV("MyMediaScannerClient constructor");
117        jclass mediaScannerClientInterface =
118                env->FindClass(kClassMediaScannerClient);
119
120        if (mediaScannerClientInterface == NULL) {
121            ALOGE("Class %s not found", kClassMediaScannerClient);
122        } else {
123            mScanFileMethodID = env->GetMethodID(
124                                    mediaScannerClientInterface,
125                                    "scanFile",
126                                    "(Ljava/lang/String;JJZZ)V");
127
128            mHandleStringTagMethodID = env->GetMethodID(
129                                    mediaScannerClientInterface,
130                                    "handleStringTag",
131                                    "(Ljava/lang/String;Ljava/lang/String;)V");
132
133            mSetMimeTypeMethodID = env->GetMethodID(
134                                    mediaScannerClientInterface,
135                                    "setMimeType",
136                                    "(Ljava/lang/String;)V");
137        }
138    }
139
140    virtual ~MyMediaScannerClient()
141    {
142        ALOGV("MyMediaScannerClient destructor");
143        mEnv->DeleteGlobalRef(mClient);
144    }
145
146    virtual status_t scanFile(const char* path, long long lastModified,
147            long long fileSize, bool isDirectory, bool noMedia)
148    {
149        ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
150            path, lastModified, fileSize, isDirectory);
151
152        jstring pathStr;
153        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
154            mEnv->ExceptionClear();
155            return NO_MEMORY;
156        }
157
158        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
159                fileSize, isDirectory, noMedia);
160
161        mEnv->DeleteLocalRef(pathStr);
162        return checkAndClearExceptionFromCallback(mEnv, "scanFile");
163    }
164
165    virtual status_t handleStringTag(const char* name, const char* value)
166    {
167        ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
168        jstring nameStr, valueStr;
169        if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
170            mEnv->ExceptionClear();
171            return NO_MEMORY;
172        }
173        char *cleaned = NULL;
174        if (!isValidUtf8(value)) {
175            cleaned = strdup(value);
176            char *chp = cleaned;
177            char ch;
178            while ((ch = *chp)) {
179                if (ch & 0x80) {
180                    *chp = '?';
181                }
182                chp++;
183            }
184            value = cleaned;
185        }
186        valueStr = mEnv->NewStringUTF(value);
187        free(cleaned);
188        if (valueStr == NULL) {
189            mEnv->DeleteLocalRef(nameStr);
190            mEnv->ExceptionClear();
191            return NO_MEMORY;
192        }
193
194        mEnv->CallVoidMethod(
195            mClient, mHandleStringTagMethodID, nameStr, valueStr);
196
197        mEnv->DeleteLocalRef(nameStr);
198        mEnv->DeleteLocalRef(valueStr);
199        return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
200    }
201
202    virtual status_t setMimeType(const char* mimeType)
203    {
204        ALOGV("setMimeType: %s", mimeType);
205        jstring mimeTypeStr;
206        if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
207            mEnv->ExceptionClear();
208            return NO_MEMORY;
209        }
210
211        mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
212
213        mEnv->DeleteLocalRef(mimeTypeStr);
214        return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
215    }
216
217private:
218    JNIEnv *mEnv;
219    jobject mClient;
220    jmethodID mScanFileMethodID;
221    jmethodID mHandleStringTagMethodID;
222    jmethodID mSetMimeTypeMethodID;
223};
224
225
226static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
227{
228    return (MediaScanner *) env->GetIntField(thiz, fields.context);
229}
230
231static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
232{
233    env->SetIntField(thiz, fields.context, (int)s);
234}
235
236static void
237android_media_MediaScanner_processDirectory(
238        JNIEnv *env, jobject thiz, jstring path, jobject client)
239{
240    ALOGV("processDirectory");
241    MediaScanner *mp = getNativeScanner_l(env, thiz);
242    if (mp == NULL) {
243        jniThrowException(env, kRunTimeException, "No scanner available");
244        return;
245    }
246
247    if (path == NULL) {
248        jniThrowException(env, kIllegalArgumentException, NULL);
249        return;
250    }
251
252    const char *pathStr = env->GetStringUTFChars(path, NULL);
253    if (pathStr == NULL) {  // Out of memory
254        return;
255    }
256
257    MyMediaScannerClient myClient(env, client);
258    MediaScanResult result = mp->processDirectory(pathStr, myClient);
259    if (result == MEDIA_SCAN_RESULT_ERROR) {
260        ALOGE("An error occurred while scanning directory '%s'.", pathStr);
261    }
262    env->ReleaseStringUTFChars(path, pathStr);
263}
264
265static void
266android_media_MediaScanner_processFile(
267        JNIEnv *env, jobject thiz, jstring path,
268        jstring mimeType, jobject client)
269{
270    ALOGV("processFile");
271
272    // Lock already hold by processDirectory
273    MediaScanner *mp = getNativeScanner_l(env, thiz);
274    if (mp == NULL) {
275        jniThrowException(env, kRunTimeException, "No scanner available");
276        return;
277    }
278
279    if (path == NULL) {
280        jniThrowException(env, kIllegalArgumentException, NULL);
281        return;
282    }
283
284    const char *pathStr = env->GetStringUTFChars(path, NULL);
285    if (pathStr == NULL) {  // Out of memory
286        return;
287    }
288
289    const char *mimeTypeStr =
290        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
291    if (mimeType && mimeTypeStr == NULL) {  // Out of memory
292        // ReleaseStringUTFChars can be called with an exception pending.
293        env->ReleaseStringUTFChars(path, pathStr);
294        return;
295    }
296
297    MyMediaScannerClient myClient(env, client);
298    MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
299    if (result == MEDIA_SCAN_RESULT_ERROR) {
300        ALOGE("An error occurred while scanning file '%s'.", pathStr);
301    }
302    env->ReleaseStringUTFChars(path, pathStr);
303    if (mimeType) {
304        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
305    }
306}
307
308static void
309android_media_MediaScanner_setLocale(
310        JNIEnv *env, jobject thiz, jstring locale)
311{
312    ALOGV("setLocale");
313    MediaScanner *mp = getNativeScanner_l(env, thiz);
314    if (mp == NULL) {
315        jniThrowException(env, kRunTimeException, "No scanner available");
316        return;
317    }
318
319    if (locale == NULL) {
320        jniThrowException(env, kIllegalArgumentException, NULL);
321        return;
322    }
323    const char *localeStr = env->GetStringUTFChars(locale, NULL);
324    if (localeStr == NULL) {  // Out of memory
325        return;
326    }
327    mp->setLocale(localeStr);
328
329    env->ReleaseStringUTFChars(locale, localeStr);
330}
331
332static jbyteArray
333android_media_MediaScanner_extractAlbumArt(
334        JNIEnv *env, jobject thiz, jobject fileDescriptor)
335{
336    ALOGV("extractAlbumArt");
337    MediaScanner *mp = getNativeScanner_l(env, thiz);
338    if (mp == NULL) {
339        jniThrowException(env, kRunTimeException, "No scanner available");
340        return NULL;
341    }
342
343    if (fileDescriptor == NULL) {
344        jniThrowException(env, kIllegalArgumentException, NULL);
345        return NULL;
346    }
347
348    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
349    char* data = mp->extractAlbumArt(fd);
350    if (!data) {
351        return NULL;
352    }
353    long len = *((long*)data);
354
355    jbyteArray array = env->NewByteArray(len);
356    if (array != NULL) {
357        jbyte* bytes = env->GetByteArrayElements(array, NULL);
358        memcpy(bytes, data + 4, len);
359        env->ReleaseByteArrayElements(array, bytes, 0);
360    }
361
362done:
363    free(data);
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.
374static void
375android_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", "I");
384    if (fields.context == NULL) {
385        return;
386    }
387}
388
389static void
390android_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->SetIntField(thiz, fields.context, (int)mp);
401}
402
403static void
404android_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
415static 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
461int register_android_media_MediaScanner(JNIEnv *env)
462{
463    return AndroidRuntime::registerNativeMethods(env,
464                kClassMediaScanner, gMethods, NELEM(gMethods));
465}
466