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
31using namespace android;
32
33
34static const char* const kClassMediaScannerClient =
35        "android/media/MediaScannerClient";
36
37static const char* const kClassMediaScanner =
38        "android/media/MediaScanner";
39
40static const char* const kRunTimeException =
41        "java/lang/RuntimeException";
42
43static const char* const kIllegalArgumentException =
44        "java/lang/IllegalArgumentException";
45
46struct fields_t {
47    jfieldID    context;
48};
49static fields_t fields;
50
51static 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
62static 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
108class MyMediaScannerClient : public MediaScannerClient
109{
110public:
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
142    virtual ~MyMediaScannerClient()
143    {
144        ALOGV("MyMediaScannerClient destructor");
145        mEnv->DeleteGlobalRef(mClient);
146    }
147
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
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
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
219private:
220    JNIEnv *mEnv;
221    jobject mClient;
222    jmethodID mScanFileMethodID;
223    jmethodID mHandleStringTagMethodID;
224    jmethodID mSetMimeTypeMethodID;
225};
226
227
228static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
229{
230    return (MediaScanner *) env->GetLongField(thiz, fields.context);
231}
232
233static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
234{
235    env->SetLongField(thiz, fields.context, (jlong)s);
236}
237
238static void
239android_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
267static void
268android_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
310static void
311android_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
334static jbyteArray
335android_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.
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", "J");
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->SetLongField(thiz, fields.context, (jlong)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 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
461int register_android_media_MediaScanner(JNIEnv *env)
462{
463    return AndroidRuntime::registerNativeMethods(env,
464                kClassMediaScanner, gMethods, NELEM(gMethods));
465}
466