android_media_MediaScanner.cpp revision af16d0b30e89f31b7617424c8706553ff571d08d
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
49class MyMediaScannerClient : public MediaScannerClient
50{
51public:
52    MyMediaScannerClient(JNIEnv *env, jobject client)
53        :   mEnv(env),
54            mClient(env->NewGlobalRef(client)),
55            mScanFileMethodID(0),
56            mHandleStringTagMethodID(0),
57            mSetMimeTypeMethodID(0)
58    {
59        LOGV("MyMediaScannerClient constructor");
60        jclass mediaScannerClientInterface =
61                env->FindClass(kClassMediaScannerClient);
62
63        if (mediaScannerClientInterface == NULL) {
64            LOGE("Class %s not found", kClassMediaScannerClient);
65        } else {
66            mScanFileMethodID = env->GetMethodID(
67                                    mediaScannerClientInterface,
68                                    "scanFile",
69                                    "(Ljava/lang/String;JJZZ)V");
70
71            mHandleStringTagMethodID = env->GetMethodID(
72                                    mediaScannerClientInterface,
73                                    "handleStringTag",
74                                    "(Ljava/lang/String;Ljava/lang/String;)V");
75
76            mSetMimeTypeMethodID = env->GetMethodID(
77                                    mediaScannerClientInterface,
78                                    "setMimeType",
79                                    "(Ljava/lang/String;)V");
80        }
81    }
82
83    virtual ~MyMediaScannerClient()
84    {
85        LOGV("MyMediaScannerClient destructor");
86        mEnv->DeleteGlobalRef(mClient);
87    }
88
89    // Returns true if it succeeded, false if an exception occured
90    // in the Java code
91    virtual bool scanFile(const char* path, long long lastModified,
92            long long fileSize, bool isDirectory, bool noMedia)
93    {
94        LOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
95            path, lastModified, fileSize, isDirectory);
96
97        jstring pathStr;
98        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
99            return false;
100        }
101
102        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
103                fileSize, isDirectory, noMedia);
104
105        mEnv->DeleteLocalRef(pathStr);
106        return (!mEnv->ExceptionCheck());
107    }
108
109    // Returns true if it succeeded, false if an exception occured
110    // in the Java code
111    virtual bool handleStringTag(const char* name, const char* value)
112    {
113        LOGV("handleStringTag: name(%s) and value(%s)", name, value);
114        jstring nameStr, valueStr;
115        if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
116            return false;
117        }
118        if ((valueStr = mEnv->NewStringUTF(value)) == NULL) {
119            return false;
120        }
121
122        mEnv->CallVoidMethod(
123            mClient, mHandleStringTagMethodID, nameStr, valueStr);
124
125        mEnv->DeleteLocalRef(nameStr);
126        mEnv->DeleteLocalRef(valueStr);
127        return (!mEnv->ExceptionCheck());
128    }
129
130    // Returns true if it succeeded, false if an exception occured
131    // in the Java code
132    virtual bool setMimeType(const char* mimeType)
133    {
134        LOGV("setMimeType: %s", mimeType);
135        jstring mimeTypeStr;
136        if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
137            return false;
138        }
139
140        mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
141
142        mEnv->DeleteLocalRef(mimeTypeStr);
143        return (!mEnv->ExceptionCheck());
144    }
145
146private:
147    JNIEnv *mEnv;
148    jobject mClient;
149    jmethodID mScanFileMethodID;
150    jmethodID mHandleStringTagMethodID;
151    jmethodID mSetMimeTypeMethodID;
152};
153
154
155static bool ExceptionCheck(void* env)
156{
157    LOGV("ExceptionCheck");
158    return ((JNIEnv *)env)->ExceptionCheck();
159}
160
161static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
162{
163    return (MediaScanner *) env->GetIntField(thiz, fields.context);
164}
165
166static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
167{
168    env->SetIntField(thiz, fields.context, (int)s);
169}
170
171static void
172android_media_MediaScanner_processDirectory(
173        JNIEnv *env, jobject thiz, jstring path, jobject client)
174{
175    LOGV("processDirectory");
176    MediaScanner *mp = getNativeScanner_l(env, thiz);
177    if (mp == NULL) {
178        jniThrowException(env, kRunTimeException, "No scanner available");
179        return;
180    }
181
182    if (path == NULL) {
183        jniThrowException(env, kIllegalArgumentException, NULL);
184        return;
185    }
186
187    const char *pathStr = env->GetStringUTFChars(path, NULL);
188    if (pathStr == NULL) {  // Out of memory
189        return;
190    }
191
192    MyMediaScannerClient myClient(env, client);
193    mp->processDirectory(pathStr, myClient, ExceptionCheck, env);
194    env->ReleaseStringUTFChars(path, pathStr);
195}
196
197static void
198android_media_MediaScanner_processFile(
199        JNIEnv *env, jobject thiz, jstring path,
200        jstring mimeType, jobject client)
201{
202    LOGV("processFile");
203
204    // Lock already hold by processDirectory
205    MediaScanner *mp = getNativeScanner_l(env, thiz);
206    if (mp == NULL) {
207        jniThrowException(env, kRunTimeException, "No scanner available");
208        return;
209    }
210
211    if (path == NULL) {
212        jniThrowException(env, kIllegalArgumentException, NULL);
213        return;
214    }
215
216    const char *pathStr = env->GetStringUTFChars(path, NULL);
217    if (pathStr == NULL) {  // Out of memory
218        return;
219    }
220
221    const char *mimeTypeStr =
222        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
223    if (mimeType && mimeTypeStr == NULL) {  // Out of memory
224        // ReleaseStringUTFChars can be called with an exception pending.
225        env->ReleaseStringUTFChars(path, pathStr);
226        return;
227    }
228
229    MyMediaScannerClient myClient(env, client);
230    mp->processFile(pathStr, mimeTypeStr, myClient);
231    env->ReleaseStringUTFChars(path, pathStr);
232    if (mimeType) {
233        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
234    }
235}
236
237static void
238android_media_MediaScanner_setLocale(
239        JNIEnv *env, jobject thiz, jstring locale)
240{
241    LOGV("setLocale");
242    MediaScanner *mp = getNativeScanner_l(env, thiz);
243    if (mp == NULL) {
244        jniThrowException(env, kRunTimeException, "No scanner available");
245        return;
246    }
247
248    if (locale == NULL) {
249        jniThrowException(env, kIllegalArgumentException, NULL);
250        return;
251    }
252    const char *localeStr = env->GetStringUTFChars(locale, NULL);
253    if (localeStr == NULL) {  // Out of memory
254        return;
255    }
256    mp->setLocale(localeStr);
257
258    env->ReleaseStringUTFChars(locale, localeStr);
259}
260
261static jbyteArray
262android_media_MediaScanner_extractAlbumArt(
263        JNIEnv *env, jobject thiz, jobject fileDescriptor)
264{
265    LOGV("extractAlbumArt");
266    MediaScanner *mp = getNativeScanner_l(env, thiz);
267    if (mp == NULL) {
268        jniThrowException(env, kRunTimeException, "No scanner available");
269        return NULL;
270    }
271
272    if (fileDescriptor == NULL) {
273        jniThrowException(env, kIllegalArgumentException, NULL);
274        return NULL;
275    }
276
277    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
278    char* data = mp->extractAlbumArt(fd);
279    if (!data) {
280        return NULL;
281    }
282    long len = *((long*)data);
283
284    jbyteArray array = env->NewByteArray(len);
285    if (array != NULL) {
286        jbyte* bytes = env->GetByteArrayElements(array, NULL);
287        memcpy(bytes, data + 4, len);
288        env->ReleaseByteArrayElements(array, bytes, 0);
289    }
290
291done:
292    free(data);
293    // if NewByteArray() returned NULL, an out-of-memory
294    // exception will have been raised. I just want to
295    // return null in that case.
296    env->ExceptionClear();
297    return array;
298}
299
300// This function gets a field ID, which in turn causes class initialization.
301// It is called from a static block in MediaScanner, which won't run until the
302// first time an instance of this class is used.
303static void
304android_media_MediaScanner_native_init(JNIEnv *env)
305{
306    LOGV("native_init");
307    jclass clazz = env->FindClass(kClassMediaScanner);
308    if (clazz == NULL) {
309        return;
310    }
311
312    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
313    if (fields.context == NULL) {
314        return;
315    }
316}
317
318static void
319android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
320{
321    LOGV("native_setup");
322    MediaScanner *mp = new StagefrightMediaScanner;
323
324    if (mp == NULL) {
325        jniThrowException(env, kRunTimeException, "Out of memory");
326        return;
327    }
328
329    env->SetIntField(thiz, fields.context, (int)mp);
330}
331
332static void
333android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
334{
335    LOGV("native_finalize");
336    MediaScanner *mp = getNativeScanner_l(env, thiz);
337    if (mp == 0) {
338        return;
339    }
340    delete mp;
341    setNativeScanner_l(env, thiz, 0);
342}
343
344static JNINativeMethod gMethods[] = {
345    {
346        "processDirectory",
347        "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
348        (void *)android_media_MediaScanner_processDirectory
349    },
350
351    {
352        "processFile",
353        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
354        (void *)android_media_MediaScanner_processFile
355    },
356
357    {
358        "setLocale",
359        "(Ljava/lang/String;)V",
360        (void *)android_media_MediaScanner_setLocale
361    },
362
363    {
364        "extractAlbumArt",
365        "(Ljava/io/FileDescriptor;)[B",
366        (void *)android_media_MediaScanner_extractAlbumArt
367    },
368
369    {
370        "native_init",
371        "()V",
372        (void *)android_media_MediaScanner_native_init
373    },
374
375    {
376        "native_setup",
377        "()V",
378        (void *)android_media_MediaScanner_native_setup
379    },
380
381    {
382        "native_finalize",
383        "()V",
384        (void *)android_media_MediaScanner_native_finalize
385    },
386};
387
388// This function only registers the native methods, and is called from
389// JNI_OnLoad in android_media_MediaPlayer.cpp
390int register_android_media_MediaScanner(JNIEnv *env)
391{
392    return AndroidRuntime::registerNativeMethods(env,
393                kClassMediaScanner, gMethods, NELEM(gMethods));
394}
395