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