1 /* //device/libs/media_jni/MediaScanner.cpp 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_TAG "MediaScanner" 19 #include "utils/Log.h" 20 21 #include <media/mediascanner.h> 22 #include <stdio.h> 23 #include <assert.h> 24 #include <limits.h> 25 #include <unistd.h> 26 #include <fcntl.h> 27 #include <cutils/properties.h> 28 #include <utils/threads.h> 29 30 #include "jni.h" 31 #include "JNIHelp.h" 32 #include "android_runtime/AndroidRuntime.h" 33 34 #ifndef NO_OPENCORE 35 #include "pvmediascanner.h" 36 #endif 37 38 #if BUILD_WITH_FULL_STAGEFRIGHT 39 #include <media/stagefright/StagefrightMediaScanner.h> 40 #endif 41 42 // ---------------------------------------------------------------------------- 43 44 using namespace android; 45 46 // ---------------------------------------------------------------------------- 47 48 struct fields_t { 49 jfieldID context; 50 }; 51 static fields_t fields; 52 53 // ---------------------------------------------------------------------------- 54 55 class MyMediaScannerClient : public MediaScannerClient 56 { 57 public: 58 MyMediaScannerClient(JNIEnv *env, jobject client) 59 : mEnv(env), 60 mClient(env->NewGlobalRef(client)), 61 mScanFileMethodID(0), 62 mHandleStringTagMethodID(0), 63 mSetMimeTypeMethodID(0) 64 { 65 jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient"); 66 if (mediaScannerClientInterface == NULL) { 67 fprintf(stderr, "android/media/MediaScannerClient not found\n"); 68 } 69 else { 70 mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile", 71 "(Ljava/lang/String;JJ)V"); 72 mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag", 73 "(Ljava/lang/String;Ljava/lang/String;)V"); 74 mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType", 75 "(Ljava/lang/String;)V"); 76 mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder", 77 "(Ljava/lang/String;)V"); 78 } 79 } 80 81 virtual ~MyMediaScannerClient() 82 { 83 mEnv->DeleteGlobalRef(mClient); 84 } 85 86 // returns true if it succeeded, false if an exception occured in the Java code 87 virtual bool scanFile(const char* path, long long lastModified, long long fileSize) 88 { 89 jstring pathStr; 90 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; 91 92 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); 93 94 mEnv->DeleteLocalRef(pathStr); 95 return (!mEnv->ExceptionCheck()); 96 } 97 98 // returns true if it succeeded, false if an exception occured in the Java code 99 virtual bool handleStringTag(const char* name, const char* value) 100 { 101 jstring nameStr, valueStr; 102 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) return false; 103 if ((valueStr = mEnv->NewStringUTF(value)) == NULL) return false; 104 105 mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr, valueStr); 106 107 mEnv->DeleteLocalRef(nameStr); 108 mEnv->DeleteLocalRef(valueStr); 109 return (!mEnv->ExceptionCheck()); 110 } 111 112 // returns true if it succeeded, false if an exception occured in the Java code 113 virtual bool setMimeType(const char* mimeType) 114 { 115 jstring mimeTypeStr; 116 if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) return false; 117 118 mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); 119 120 mEnv->DeleteLocalRef(mimeTypeStr); 121 return (!mEnv->ExceptionCheck()); 122 } 123 124 // returns true if it succeeded, false if an exception occured in the Java code 125 virtual bool addNoMediaFolder(const char* path) 126 { 127 jstring pathStr; 128 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; 129 130 mEnv->CallVoidMethod(mClient, mAddNoMediaFolderMethodID, pathStr); 131 132 mEnv->DeleteLocalRef(pathStr); 133 return (!mEnv->ExceptionCheck()); 134 } 135 136 137 private: 138 JNIEnv *mEnv; 139 jobject mClient; 140 jmethodID mScanFileMethodID; 141 jmethodID mHandleStringTagMethodID; 142 jmethodID mSetMimeTypeMethodID; 143 jmethodID mAddNoMediaFolderMethodID; 144 }; 145 146 147 // ---------------------------------------------------------------------------- 148 149 static bool ExceptionCheck(void* env) 150 { 151 return ((JNIEnv *)env)->ExceptionCheck(); 152 } 153 154 static void 155 android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) 156 { 157 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); 158 159 if (path == NULL) { 160 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 161 return; 162 } 163 if (extensions == NULL) { 164 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 165 return; 166 } 167 168 const char *pathStr = env->GetStringUTFChars(path, NULL); 169 if (pathStr == NULL) { // Out of memory 170 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 171 return; 172 } 173 const char *extensionsStr = env->GetStringUTFChars(extensions, NULL); 174 if (extensionsStr == NULL) { // Out of memory 175 env->ReleaseStringUTFChars(path, pathStr); 176 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 177 return; 178 } 179 180 MyMediaScannerClient myClient(env, client); 181 mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); 182 env->ReleaseStringUTFChars(path, pathStr); 183 env->ReleaseStringUTFChars(extensions, extensionsStr); 184 } 185 186 static void 187 android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) 188 { 189 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); 190 191 if (path == NULL) { 192 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 193 return; 194 } 195 196 const char *pathStr = env->GetStringUTFChars(path, NULL); 197 if (pathStr == NULL) { // Out of memory 198 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 199 return; 200 } 201 const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); 202 if (mimeType && mimeTypeStr == NULL) { // Out of memory 203 env->ReleaseStringUTFChars(path, pathStr); 204 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 205 return; 206 } 207 208 MyMediaScannerClient myClient(env, client); 209 mp->processFile(pathStr, mimeTypeStr, myClient); 210 env->ReleaseStringUTFChars(path, pathStr); 211 if (mimeType) { 212 env->ReleaseStringUTFChars(mimeType, mimeTypeStr); 213 } 214 } 215 216 static void 217 android_media_MediaScanner_setLocale(JNIEnv *env, jobject thiz, jstring locale) 218 { 219 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); 220 221 if (locale == NULL) { 222 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 223 return; 224 } 225 const char *localeStr = env->GetStringUTFChars(locale, NULL); 226 if (localeStr == NULL) { // Out of memory 227 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 228 return; 229 } 230 mp->setLocale(localeStr); 231 232 env->ReleaseStringUTFChars(locale, localeStr); 233 } 234 235 static jbyteArray 236 android_media_MediaScanner_extractAlbumArt(JNIEnv *env, jobject thiz, jobject fileDescriptor) 237 { 238 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); 239 240 if (fileDescriptor == NULL) { 241 jniThrowException(env, "java/lang/IllegalArgumentException", NULL); 242 return NULL; 243 } 244 245 int fd = getParcelFileDescriptorFD(env, fileDescriptor); 246 char* data = mp->extractAlbumArt(fd); 247 if (!data) { 248 return NULL; 249 } 250 long len = *((long*)data); 251 252 jbyteArray array = env->NewByteArray(len); 253 if (array != NULL) { 254 jbyte* bytes = env->GetByteArrayElements(array, NULL); 255 memcpy(bytes, data + 4, len); 256 env->ReleaseByteArrayElements(array, bytes, 0); 257 } 258 259 done: 260 free(data); 261 // if NewByteArray() returned NULL, an out-of-memory 262 // exception will have been raised. I just want to 263 // return null in that case. 264 env->ExceptionClear(); 265 return array; 266 } 267 268 // This function gets a field ID, which in turn causes class initialization. 269 // It is called from a static block in MediaScanner, which won't run until the 270 // first time an instance of this class is used. 271 static void 272 android_media_MediaScanner_native_init(JNIEnv *env) 273 { 274 jclass clazz; 275 276 clazz = env->FindClass("android/media/MediaScanner"); 277 if (clazz == NULL) { 278 jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaScanner"); 279 return; 280 } 281 282 fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); 283 if (fields.context == NULL) { 284 jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaScanner.mNativeContext"); 285 return; 286 } 287 } 288 289 static MediaScanner *createMediaScanner() { 290 #if BUILD_WITH_FULL_STAGEFRIGHT 291 char value[PROPERTY_VALUE_MAX]; 292 if (property_get("media.stagefright.enable-scan", value, NULL) 293 && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { 294 return new StagefrightMediaScanner; 295 } 296 #endif 297 #ifndef NO_OPENCORE 298 return new PVMediaScanner(); 299 #endif 300 301 return NULL; 302 } 303 304 static void 305 android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) 306 { 307 MediaScanner *mp = createMediaScanner(); 308 309 if (mp == NULL) { 310 jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); 311 return; 312 } 313 314 env->SetIntField(thiz, fields.context, (int)mp); 315 } 316 317 static void 318 android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz) 319 { 320 MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); 321 322 //printf("##### android_media_MediaScanner_native_finalize: ctx=0x%p\n", ctx); 323 324 if (mp == 0) 325 return; 326 327 delete mp; 328 } 329 330 // ---------------------------------------------------------------------------- 331 332 static JNINativeMethod gMethods[] = { 333 {"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 334 (void *)android_media_MediaScanner_processDirectory}, 335 {"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 336 (void *)android_media_MediaScanner_processFile}, 337 {"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale}, 338 {"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt}, 339 {"native_init", "()V", (void *)android_media_MediaScanner_native_init}, 340 {"native_setup", "()V", (void *)android_media_MediaScanner_native_setup}, 341 {"native_finalize", "()V", (void *)android_media_MediaScanner_native_finalize}, 342 }; 343 344 static const char* const kClassPathName = "android/media/MediaScanner"; 345 346 // This function only registers the native methods, and is called from 347 // JNI_OnLoad in android_media_MediaPlayer.cpp 348 int register_android_media_MediaScanner(JNIEnv *env) 349 { 350 return AndroidRuntime::registerNativeMethods(env, 351 "android/media/MediaScanner", gMethods, NELEM(gMethods)); 352 } 353 354 355