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