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 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 jboolean 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 false; 279 } 280 281 if (path == NULL) { 282 jniThrowException(env, kIllegalArgumentException, NULL); 283 return false; 284 } 285 286 const char *pathStr = env->GetStringUTFChars(path, NULL); 287 if (pathStr == NULL) { // Out of memory 288 return false; 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 false; 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 return result != MEDIA_SCAN_RESULT_ERROR; 309 } 310 311 static void 312 android_media_MediaScanner_setLocale( 313 JNIEnv *env, jobject thiz, jstring locale) 314 { 315 ALOGV("setLocale"); 316 MediaScanner *mp = getNativeScanner_l(env, thiz); 317 if (mp == NULL) { 318 jniThrowException(env, kRunTimeException, "No scanner available"); 319 return; 320 } 321 322 if (locale == NULL) { 323 jniThrowException(env, kIllegalArgumentException, NULL); 324 return; 325 } 326 const char *localeStr = env->GetStringUTFChars(locale, NULL); 327 if (localeStr == NULL) { // Out of memory 328 return; 329 } 330 mp->setLocale(localeStr); 331 332 env->ReleaseStringUTFChars(locale, localeStr); 333 } 334 335 static jbyteArray 336 android_media_MediaScanner_extractAlbumArt( 337 JNIEnv *env, jobject thiz, jobject fileDescriptor) 338 { 339 ALOGV("extractAlbumArt"); 340 MediaScanner *mp = getNativeScanner_l(env, thiz); 341 if (mp == NULL) { 342 jniThrowException(env, kRunTimeException, "No scanner available"); 343 return NULL; 344 } 345 346 if (fileDescriptor == NULL) { 347 jniThrowException(env, kIllegalArgumentException, NULL); 348 return NULL; 349 } 350 351 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 352 MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd); 353 if (mediaAlbumArt == NULL) { 354 return NULL; 355 } 356 357 jbyteArray array = env->NewByteArray(mediaAlbumArt->size()); 358 if (array != NULL) { 359 const jbyte* data = 360 reinterpret_cast<const jbyte*>(mediaAlbumArt->data()); 361 env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data); 362 } 363 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 const 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;)Z", 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