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 <media/mediascanner.h>
     23 #include <media/stagefright/StagefrightMediaScanner.h>
     24 #include <private/media/VideoFrame.h>
     25 
     26 #include "jni.h"
     27 #include "JNIHelp.h"
     28 #include "android_runtime/AndroidRuntime.h"
     29 #include "android_runtime/Log.h"
     30 
     31 using namespace android;
     32 
     33 
     34 static const char* const kClassMediaScannerClient =
     35         "android/media/MediaScannerClient";
     36 
     37 static const char* const kClassMediaScanner =
     38         "android/media/MediaScanner";
     39 
     40 static const char* const kRunTimeException =
     41         "java/lang/RuntimeException";
     42 
     43 static const char* const kIllegalArgumentException =
     44         "java/lang/IllegalArgumentException";
     45 
     46 struct fields_t {
     47     jfieldID    context;
     48 };
     49 static fields_t fields;
     50 
     51 static 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
     62 static 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 
    108 class MyMediaScannerClient : public MediaScannerClient
    109 {
    110 public:
    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 
    219 private:
    220     JNIEnv *mEnv;
    221     jobject mClient;
    222     jmethodID mScanFileMethodID;
    223     jmethodID mHandleStringTagMethodID;
    224     jmethodID mSetMimeTypeMethodID;
    225 };
    226 
    227 
    228 static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
    229 {
    230     return (MediaScanner *) env->GetLongField(thiz, fields.context);
    231 }
    232 
    233 static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
    234 {
    235     env->SetLongField(thiz, fields.context, (jlong)s);
    236 }
    237 
    238 static void
    239 android_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 
    267 static void
    268 android_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 
    310 static void
    311 android_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 
    334 static jbyteArray
    335 android_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 done:
    364     free(mediaAlbumArt);
    365     // if NewByteArray() returned NULL, an out-of-memory
    366     // exception will have been raised. I just want to
    367     // return null in that case.
    368     env->ExceptionClear();
    369     return array;
    370 }
    371 
    372 // This function gets a field ID, which in turn causes class initialization.
    373 // It is called from a static block in MediaScanner, which won't run until the
    374 // first time an instance of this class is used.
    375 static void
    376 android_media_MediaScanner_native_init(JNIEnv *env)
    377 {
    378     ALOGV("native_init");
    379     jclass clazz = env->FindClass(kClassMediaScanner);
    380     if (clazz == NULL) {
    381         return;
    382     }
    383 
    384     fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    385     if (fields.context == NULL) {
    386         return;
    387     }
    388 }
    389 
    390 static void
    391 android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
    392 {
    393     ALOGV("native_setup");
    394     MediaScanner *mp = new StagefrightMediaScanner;
    395 
    396     if (mp == NULL) {
    397         jniThrowException(env, kRunTimeException, "Out of memory");
    398         return;
    399     }
    400 
    401     env->SetLongField(thiz, fields.context, (jlong)mp);
    402 }
    403 
    404 static void
    405 android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
    406 {
    407     ALOGV("native_finalize");
    408     MediaScanner *mp = getNativeScanner_l(env, thiz);
    409     if (mp == 0) {
    410         return;
    411     }
    412     delete mp;
    413     setNativeScanner_l(env, thiz, 0);
    414 }
    415 
    416 static JNINativeMethod gMethods[] = {
    417     {
    418         "processDirectory",
    419         "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    420         (void *)android_media_MediaScanner_processDirectory
    421     },
    422 
    423     {
    424         "processFile",
    425         "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
    426         (void *)android_media_MediaScanner_processFile
    427     },
    428 
    429     {
    430         "setLocale",
    431         "(Ljava/lang/String;)V",
    432         (void *)android_media_MediaScanner_setLocale
    433     },
    434 
    435     {
    436         "extractAlbumArt",
    437         "(Ljava/io/FileDescriptor;)[B",
    438         (void *)android_media_MediaScanner_extractAlbumArt
    439     },
    440 
    441     {
    442         "native_init",
    443         "()V",
    444         (void *)android_media_MediaScanner_native_init
    445     },
    446 
    447     {
    448         "native_setup",
    449         "()V",
    450         (void *)android_media_MediaScanner_native_setup
    451     },
    452 
    453     {
    454         "native_finalize",
    455         "()V",
    456         (void *)android_media_MediaScanner_native_finalize
    457     },
    458 };
    459 
    460 // This function only registers the native methods, and is called from
    461 // JNI_OnLoad in android_media_MediaPlayer.cpp
    462 int register_android_media_MediaScanner(JNIEnv *env)
    463 {
    464     return AndroidRuntime::registerNativeMethods(env,
    465                 kClassMediaScanner, gMethods, NELEM(gMethods));
    466 }
    467