android_media_MediaScanner.cpp revision 87eac99a21772ae56018cb81db6966557b459554
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 25#include "jni.h" 26#include "JNIHelp.h" 27#include "android_runtime/AndroidRuntime.h" 28#include "android_runtime/Log.h" 29 30using namespace android; 31 32 33static const char* const kClassMediaScannerClient = 34 "android/media/MediaScannerClient"; 35 36static const char* const kClassMediaScanner = 37 "android/media/MediaScanner"; 38 39static const char* const kRunTimeException = 40 "java/lang/RuntimeException"; 41 42static const char* const kIllegalArgumentException = 43 "java/lang/IllegalArgumentException"; 44 45struct fields_t { 46 jfieldID context; 47}; 48static fields_t fields; 49 50static 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// stolen from dalvik/vm/checkJni.cpp 61static bool isValidUtf8(const char* bytes) { 62 while (*bytes != '\0') { 63 unsigned char utf8 = *(bytes++); 64 // Switch on the high four bits. 65 switch (utf8 >> 4) { 66 case 0x00: 67 case 0x01: 68 case 0x02: 69 case 0x03: 70 case 0x04: 71 case 0x05: 72 case 0x06: 73 case 0x07: 74 // Bit pattern 0xxx. No need for any extra bytes. 75 break; 76 case 0x08: 77 case 0x09: 78 case 0x0a: 79 case 0x0b: 80 case 0x0f: 81 /* 82 * Bit pattern 10xx or 1111, which are illegal start bytes. 83 * Note: 1111 is valid for normal UTF-8, but not the 84 * modified UTF-8 used here. 85 */ 86 return false; 87 case 0x0e: 88 // Bit pattern 1110, so there are two additional bytes. 89 utf8 = *(bytes++); 90 if ((utf8 & 0xc0) != 0x80) { 91 return false; 92 } 93 // Fall through to take care of the final byte. 94 case 0x0c: 95 case 0x0d: 96 // Bit pattern 110x, so there is one additional byte. 97 utf8 = *(bytes++); 98 if ((utf8 & 0xc0) != 0x80) { 99 return false; 100 } 101 break; 102 } 103 } 104 return true; 105} 106 107class MyMediaScannerClient : public MediaScannerClient 108{ 109public: 110 MyMediaScannerClient(JNIEnv *env, jobject client) 111 : mEnv(env), 112 mClient(env->NewGlobalRef(client)), 113 mScanFileMethodID(0), 114 mHandleStringTagMethodID(0), 115 mSetMimeTypeMethodID(0) 116 { 117 ALOGV("MyMediaScannerClient constructor"); 118 jclass mediaScannerClientInterface = 119 env->FindClass(kClassMediaScannerClient); 120 121 if (mediaScannerClientInterface == NULL) { 122 ALOGE("Class %s not found", kClassMediaScannerClient); 123 } else { 124 mScanFileMethodID = env->GetMethodID( 125 mediaScannerClientInterface, 126 "scanFile", 127 "(Ljava/lang/String;JJZZ)V"); 128 129 mHandleStringTagMethodID = env->GetMethodID( 130 mediaScannerClientInterface, 131 "handleStringTag", 132 "(Ljava/lang/String;Ljava/lang/String;)V"); 133 134 mSetMimeTypeMethodID = env->GetMethodID( 135 mediaScannerClientInterface, 136 "setMimeType", 137 "(Ljava/lang/String;)V"); 138 } 139 } 140 141 virtual ~MyMediaScannerClient() 142 { 143 ALOGV("MyMediaScannerClient destructor"); 144 mEnv->DeleteGlobalRef(mClient); 145 } 146 147 virtual status_t scanFile(const char* path, long long lastModified, 148 long long fileSize, bool isDirectory, bool noMedia) 149 { 150 ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)", 151 path, lastModified, fileSize, isDirectory); 152 153 jstring pathStr; 154 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { 155 mEnv->ExceptionClear(); 156 return NO_MEMORY; 157 } 158 159 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, 160 fileSize, isDirectory, noMedia); 161 162 mEnv->DeleteLocalRef(pathStr); 163 return checkAndClearExceptionFromCallback(mEnv, "scanFile"); 164 } 165 166 virtual status_t handleStringTag(const char* name, const char* value) 167 { 168 ALOGV("handleStringTag: name(%s) and value(%s)", name, value); 169 jstring nameStr, valueStr; 170 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { 171 mEnv->ExceptionClear(); 172 return NO_MEMORY; 173 } 174 char *cleaned = NULL; 175 if (!isValidUtf8(value)) { 176 cleaned = strdup(value); 177 char *chp = cleaned; 178 char ch; 179 while ((ch = *chp)) { 180 if (ch & 0x80) { 181 *chp = '?'; 182 } 183 chp++; 184 } 185 value = cleaned; 186 } 187 valueStr = mEnv->NewStringUTF(value); 188 free(cleaned); 189 if (valueStr == NULL) { 190 mEnv->DeleteLocalRef(nameStr); 191 mEnv->ExceptionClear(); 192 return NO_MEMORY; 193 } 194 195 mEnv->CallVoidMethod( 196 mClient, mHandleStringTagMethodID, nameStr, valueStr); 197 198 mEnv->DeleteLocalRef(nameStr); 199 mEnv->DeleteLocalRef(valueStr); 200 return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); 201 } 202 203 virtual status_t setMimeType(const char* mimeType) 204 { 205 ALOGV("setMimeType: %s", mimeType); 206 jstring mimeTypeStr; 207 if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) { 208 mEnv->ExceptionClear(); 209 return NO_MEMORY; 210 } 211 212 mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); 213 214 mEnv->DeleteLocalRef(mimeTypeStr); 215 return checkAndClearExceptionFromCallback(mEnv, "setMimeType"); 216 } 217 218private: 219 JNIEnv *mEnv; 220 jobject mClient; 221 jmethodID mScanFileMethodID; 222 jmethodID mHandleStringTagMethodID; 223 jmethodID mSetMimeTypeMethodID; 224}; 225 226 227static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz) 228{ 229 return (MediaScanner *) env->GetIntField(thiz, fields.context); 230} 231 232static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s) 233{ 234 env->SetIntField(thiz, fields.context, (int)s); 235} 236 237static void 238android_media_MediaScanner_processDirectory( 239 JNIEnv *env, jobject thiz, jstring path, jobject client) 240{ 241 ALOGV("processDirectory"); 242 MediaScanner *mp = getNativeScanner_l(env, thiz); 243 if (mp == NULL) { 244 jniThrowException(env, kRunTimeException, "No scanner available"); 245 return; 246 } 247 248 if (path == NULL) { 249 jniThrowException(env, kIllegalArgumentException, NULL); 250 return; 251 } 252 253 const char *pathStr = env->GetStringUTFChars(path, NULL); 254 if (pathStr == NULL) { // Out of memory 255 return; 256 } 257 258 MyMediaScannerClient myClient(env, client); 259 MediaScanResult result = mp->processDirectory(pathStr, myClient); 260 if (result == MEDIA_SCAN_RESULT_ERROR) { 261 ALOGE("An error occurred while scanning directory '%s'.", pathStr); 262 } 263 env->ReleaseStringUTFChars(path, pathStr); 264} 265 266static void 267android_media_MediaScanner_processFile( 268 JNIEnv *env, jobject thiz, jstring path, 269 jstring mimeType, jobject client) 270{ 271 ALOGV("processFile"); 272 273 // Lock already hold by processDirectory 274 MediaScanner *mp = getNativeScanner_l(env, thiz); 275 if (mp == NULL) { 276 jniThrowException(env, kRunTimeException, "No scanner available"); 277 return; 278 } 279 280 if (path == NULL) { 281 jniThrowException(env, kIllegalArgumentException, NULL); 282 return; 283 } 284 285 const char *pathStr = env->GetStringUTFChars(path, NULL); 286 if (pathStr == NULL) { // Out of memory 287 return; 288 } 289 290 const char *mimeTypeStr = 291 (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); 292 if (mimeType && mimeTypeStr == NULL) { // Out of memory 293 // ReleaseStringUTFChars can be called with an exception pending. 294 env->ReleaseStringUTFChars(path, pathStr); 295 return; 296 } 297 298 MyMediaScannerClient myClient(env, client); 299 MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); 300 if (result == MEDIA_SCAN_RESULT_ERROR) { 301 ALOGE("An error occurred while scanning file '%s'.", pathStr); 302 } 303 env->ReleaseStringUTFChars(path, pathStr); 304 if (mimeType) { 305 env->ReleaseStringUTFChars(mimeType, mimeTypeStr); 306 } 307} 308 309static void 310android_media_MediaScanner_setLocale( 311 JNIEnv *env, jobject thiz, jstring locale) 312{ 313 ALOGV("setLocale"); 314 MediaScanner *mp = getNativeScanner_l(env, thiz); 315 if (mp == NULL) { 316 jniThrowException(env, kRunTimeException, "No scanner available"); 317 return; 318 } 319 320 if (locale == NULL) { 321 jniThrowException(env, kIllegalArgumentException, NULL); 322 return; 323 } 324 const char *localeStr = env->GetStringUTFChars(locale, NULL); 325 if (localeStr == NULL) { // Out of memory 326 return; 327 } 328 mp->setLocale(localeStr); 329 330 env->ReleaseStringUTFChars(locale, localeStr); 331} 332 333static jbyteArray 334android_media_MediaScanner_extractAlbumArt( 335 JNIEnv *env, jobject thiz, jobject fileDescriptor) 336{ 337 ALOGV("extractAlbumArt"); 338 MediaScanner *mp = getNativeScanner_l(env, thiz); 339 if (mp == NULL) { 340 jniThrowException(env, kRunTimeException, "No scanner available"); 341 return NULL; 342 } 343 344 if (fileDescriptor == NULL) { 345 jniThrowException(env, kIllegalArgumentException, NULL); 346 return NULL; 347 } 348 349 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 350 char* data = mp->extractAlbumArt(fd); 351 if (!data) { 352 return NULL; 353 } 354 long len = *((long*)data); 355 356 jbyteArray array = env->NewByteArray(len); 357 if (array != NULL) { 358 jbyte* bytes = env->GetByteArrayElements(array, NULL); 359 memcpy(bytes, data + 4, len); 360 env->ReleaseByteArrayElements(array, bytes, 0); 361 } 362 363done: 364 free(data); 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. 375static void 376android_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", "I"); 385 if (fields.context == NULL) { 386 return; 387 } 388} 389 390static void 391android_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->SetIntField(thiz, fields.context, (int)mp); 402} 403 404static void 405android_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 416static 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;)V", 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 462int register_android_media_MediaScanner(JNIEnv *env) 463{ 464 return AndroidRuntime::registerNativeMethods(env, 465 kClassMediaScanner, gMethods, NELEM(gMethods)); 466} 467