android_media_MediaScanner.cpp revision 3762c311729fe9f3af085c14c5c1fb471d994c03
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 29using namespace android; 30 31 32static const char* const kClassMediaScannerClient = 33 "android/media/MediaScannerClient"; 34 35static const char* const kClassMediaScanner = 36 "android/media/MediaScanner"; 37 38static const char* const kRunTimeException = 39 "java/lang/RuntimeException"; 40 41static const char* const kIllegalArgumentException = 42 "java/lang/IllegalArgumentException"; 43 44struct fields_t { 45 jfieldID context; 46}; 47static fields_t fields; 48 49static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { 50 if (env->ExceptionCheck()) { 51 ALOGE("An exception was thrown by callback '%s'.", methodName); 52 LOGE_EX(env); 53 env->ExceptionClear(); 54 return UNKNOWN_ERROR; 55 } 56 return OK; 57} 58 59// stolen from dalvik/vm/checkJni.cpp 60static bool isValidUtf8(const char* bytes) { 61 while (*bytes != '\0') { 62 unsigned char utf8 = *(bytes++); 63 // Switch on the high four bits. 64 switch (utf8 >> 4) { 65 case 0x00: 66 case 0x01: 67 case 0x02: 68 case 0x03: 69 case 0x04: 70 case 0x05: 71 case 0x06: 72 case 0x07: 73 // Bit pattern 0xxx. No need for any extra bytes. 74 break; 75 case 0x08: 76 case 0x09: 77 case 0x0a: 78 case 0x0b: 79 case 0x0f: 80 /* 81 * Bit pattern 10xx or 1111, which are illegal start bytes. 82 * Note: 1111 is valid for normal UTF-8, but not the 83 * modified UTF-8 used here. 84 */ 85 return false; 86 case 0x0e: 87 // Bit pattern 1110, so there are two additional bytes. 88 utf8 = *(bytes++); 89 if ((utf8 & 0xc0) != 0x80) { 90 return false; 91 } 92 // Fall through to take care of the final byte. 93 case 0x0c: 94 case 0x0d: 95 // Bit pattern 110x, so there is one additional byte. 96 utf8 = *(bytes++); 97 if ((utf8 & 0xc0) != 0x80) { 98 return false; 99 } 100 break; 101 } 102 } 103 return true; 104} 105 106class MyMediaScannerClient : public MediaScannerClient 107{ 108public: 109 MyMediaScannerClient(JNIEnv *env, jobject client) 110 : mEnv(env), 111 mClient(env->NewGlobalRef(client)), 112 mScanFileMethodID(0), 113 mHandleStringTagMethodID(0), 114 mSetMimeTypeMethodID(0) 115 { 116 ALOGV("MyMediaScannerClient constructor"); 117 jclass mediaScannerClientInterface = 118 env->FindClass(kClassMediaScannerClient); 119 120 if (mediaScannerClientInterface == NULL) { 121 ALOGE("Class %s not found", kClassMediaScannerClient); 122 } else { 123 mScanFileMethodID = env->GetMethodID( 124 mediaScannerClientInterface, 125 "scanFile", 126 "(Ljava/lang/String;JJZZ)V"); 127 128 mHandleStringTagMethodID = env->GetMethodID( 129 mediaScannerClientInterface, 130 "handleStringTag", 131 "(Ljava/lang/String;Ljava/lang/String;)V"); 132 133 mSetMimeTypeMethodID = env->GetMethodID( 134 mediaScannerClientInterface, 135 "setMimeType", 136 "(Ljava/lang/String;)V"); 137 } 138 } 139 140 virtual ~MyMediaScannerClient() 141 { 142 ALOGV("MyMediaScannerClient destructor"); 143 mEnv->DeleteGlobalRef(mClient); 144 } 145 146 virtual status_t scanFile(const char* path, long long lastModified, 147 long long fileSize, bool isDirectory, bool noMedia) 148 { 149 ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)", 150 path, lastModified, fileSize, isDirectory); 151 152 jstring pathStr; 153 if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { 154 mEnv->ExceptionClear(); 155 return NO_MEMORY; 156 } 157 158 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, 159 fileSize, isDirectory, noMedia); 160 161 mEnv->DeleteLocalRef(pathStr); 162 return checkAndClearExceptionFromCallback(mEnv, "scanFile"); 163 } 164 165 virtual status_t handleStringTag(const char* name, const char* value) 166 { 167 ALOGV("handleStringTag: name(%s) and value(%s)", name, value); 168 jstring nameStr, valueStr; 169 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { 170 mEnv->ExceptionClear(); 171 return NO_MEMORY; 172 } 173 char *cleaned = NULL; 174 if (!isValidUtf8(value)) { 175 cleaned = strdup(value); 176 char *chp = cleaned; 177 char ch; 178 while ((ch = *chp)) { 179 if (ch & 0x80) { 180 *chp = '?'; 181 } 182 chp++; 183 } 184 value = cleaned; 185 } 186 valueStr = mEnv->NewStringUTF(value); 187 free(cleaned); 188 if (valueStr == NULL) { 189 mEnv->DeleteLocalRef(nameStr); 190 mEnv->ExceptionClear(); 191 return NO_MEMORY; 192 } 193 194 mEnv->CallVoidMethod( 195 mClient, mHandleStringTagMethodID, nameStr, valueStr); 196 197 mEnv->DeleteLocalRef(nameStr); 198 mEnv->DeleteLocalRef(valueStr); 199 return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); 200 } 201 202 virtual status_t setMimeType(const char* mimeType) 203 { 204 ALOGV("setMimeType: %s", mimeType); 205 jstring mimeTypeStr; 206 if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) { 207 mEnv->ExceptionClear(); 208 return NO_MEMORY; 209 } 210 211 mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); 212 213 mEnv->DeleteLocalRef(mimeTypeStr); 214 return checkAndClearExceptionFromCallback(mEnv, "setMimeType"); 215 } 216 217private: 218 JNIEnv *mEnv; 219 jobject mClient; 220 jmethodID mScanFileMethodID; 221 jmethodID mHandleStringTagMethodID; 222 jmethodID mSetMimeTypeMethodID; 223}; 224 225 226static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz) 227{ 228 return (MediaScanner *) env->GetIntField(thiz, fields.context); 229} 230 231static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s) 232{ 233 env->SetIntField(thiz, fields.context, (int)s); 234} 235 236static void 237android_media_MediaScanner_processDirectory( 238 JNIEnv *env, jobject thiz, jstring path, jobject client) 239{ 240 ALOGV("processDirectory"); 241 MediaScanner *mp = getNativeScanner_l(env, thiz); 242 if (mp == NULL) { 243 jniThrowException(env, kRunTimeException, "No scanner available"); 244 return; 245 } 246 247 if (path == NULL) { 248 jniThrowException(env, kIllegalArgumentException, NULL); 249 return; 250 } 251 252 const char *pathStr = env->GetStringUTFChars(path, NULL); 253 if (pathStr == NULL) { // Out of memory 254 return; 255 } 256 257 MyMediaScannerClient myClient(env, client); 258 MediaScanResult result = mp->processDirectory(pathStr, myClient); 259 if (result == MEDIA_SCAN_RESULT_ERROR) { 260 ALOGE("An error occurred while scanning directory '%s'.", pathStr); 261 } 262 env->ReleaseStringUTFChars(path, pathStr); 263} 264 265static void 266android_media_MediaScanner_processFile( 267 JNIEnv *env, jobject thiz, jstring path, 268 jstring mimeType, jobject client) 269{ 270 ALOGV("processFile"); 271 272 // Lock already hold by processDirectory 273 MediaScanner *mp = getNativeScanner_l(env, thiz); 274 if (mp == NULL) { 275 jniThrowException(env, kRunTimeException, "No scanner available"); 276 return; 277 } 278 279 if (path == NULL) { 280 jniThrowException(env, kIllegalArgumentException, NULL); 281 return; 282 } 283 284 const char *pathStr = env->GetStringUTFChars(path, NULL); 285 if (pathStr == NULL) { // Out of memory 286 return; 287 } 288 289 const char *mimeTypeStr = 290 (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); 291 if (mimeType && mimeTypeStr == NULL) { // Out of memory 292 // ReleaseStringUTFChars can be called with an exception pending. 293 env->ReleaseStringUTFChars(path, pathStr); 294 return; 295 } 296 297 MyMediaScannerClient myClient(env, client); 298 MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); 299 if (result == MEDIA_SCAN_RESULT_ERROR) { 300 ALOGE("An error occurred while scanning file '%s'.", pathStr); 301 } 302 env->ReleaseStringUTFChars(path, pathStr); 303 if (mimeType) { 304 env->ReleaseStringUTFChars(mimeType, mimeTypeStr); 305 } 306} 307 308static void 309android_media_MediaScanner_setLocale( 310 JNIEnv *env, jobject thiz, jstring locale) 311{ 312 ALOGV("setLocale"); 313 MediaScanner *mp = getNativeScanner_l(env, thiz); 314 if (mp == NULL) { 315 jniThrowException(env, kRunTimeException, "No scanner available"); 316 return; 317 } 318 319 if (locale == NULL) { 320 jniThrowException(env, kIllegalArgumentException, NULL); 321 return; 322 } 323 const char *localeStr = env->GetStringUTFChars(locale, NULL); 324 if (localeStr == NULL) { // Out of memory 325 return; 326 } 327 mp->setLocale(localeStr); 328 329 env->ReleaseStringUTFChars(locale, localeStr); 330} 331 332static jbyteArray 333android_media_MediaScanner_extractAlbumArt( 334 JNIEnv *env, jobject thiz, jobject fileDescriptor) 335{ 336 ALOGV("extractAlbumArt"); 337 MediaScanner *mp = getNativeScanner_l(env, thiz); 338 if (mp == NULL) { 339 jniThrowException(env, kRunTimeException, "No scanner available"); 340 return NULL; 341 } 342 343 if (fileDescriptor == NULL) { 344 jniThrowException(env, kIllegalArgumentException, NULL); 345 return NULL; 346 } 347 348 int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); 349 char* data = mp->extractAlbumArt(fd); 350 if (!data) { 351 return NULL; 352 } 353 long len = *((long*)data); 354 355 jbyteArray array = env->NewByteArray(len); 356 if (array != NULL) { 357 jbyte* bytes = env->GetByteArrayElements(array, NULL); 358 memcpy(bytes, data + 4, len); 359 env->ReleaseByteArrayElements(array, bytes, 0); 360 } 361 362done: 363 free(data); 364 // if NewByteArray() returned NULL, an out-of-memory 365 // exception will have been raised. I just want to 366 // return null in that case. 367 env->ExceptionClear(); 368 return array; 369} 370 371// This function gets a field ID, which in turn causes class initialization. 372// It is called from a static block in MediaScanner, which won't run until the 373// first time an instance of this class is used. 374static void 375android_media_MediaScanner_native_init(JNIEnv *env) 376{ 377 ALOGV("native_init"); 378 jclass clazz = env->FindClass(kClassMediaScanner); 379 if (clazz == NULL) { 380 return; 381 } 382 383 fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); 384 if (fields.context == NULL) { 385 return; 386 } 387} 388 389static void 390android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) 391{ 392 ALOGV("native_setup"); 393 MediaScanner *mp = new StagefrightMediaScanner; 394 395 if (mp == NULL) { 396 jniThrowException(env, kRunTimeException, "Out of memory"); 397 return; 398 } 399 400 env->SetIntField(thiz, fields.context, (int)mp); 401} 402 403static void 404android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz) 405{ 406 ALOGV("native_finalize"); 407 MediaScanner *mp = getNativeScanner_l(env, thiz); 408 if (mp == 0) { 409 return; 410 } 411 delete mp; 412 setNativeScanner_l(env, thiz, 0); 413} 414 415static JNINativeMethod gMethods[] = { 416 { 417 "processDirectory", 418 "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 419 (void *)android_media_MediaScanner_processDirectory 420 }, 421 422 { 423 "processFile", 424 "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", 425 (void *)android_media_MediaScanner_processFile 426 }, 427 428 { 429 "setLocale", 430 "(Ljava/lang/String;)V", 431 (void *)android_media_MediaScanner_setLocale 432 }, 433 434 { 435 "extractAlbumArt", 436 "(Ljava/io/FileDescriptor;)[B", 437 (void *)android_media_MediaScanner_extractAlbumArt 438 }, 439 440 { 441 "native_init", 442 "()V", 443 (void *)android_media_MediaScanner_native_init 444 }, 445 446 { 447 "native_setup", 448 "()V", 449 (void *)android_media_MediaScanner_native_setup 450 }, 451 452 { 453 "native_finalize", 454 "()V", 455 (void *)android_media_MediaScanner_native_finalize 456 }, 457}; 458 459// This function only registers the native methods, and is called from 460// JNI_OnLoad in android_media_MediaPlayer.cpp 461int register_android_media_MediaScanner(JNIEnv *env) 462{ 463 return AndroidRuntime::registerNativeMethods(env, 464 kClassMediaScanner, gMethods, NELEM(gMethods)); 465} 466