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