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