/*
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaScannerJNI"
#include <utils/Log.h>
#include <utils/threads.h>
#include <media/mediascanner.h>
#include <media/stagefright/StagefrightMediaScanner.h>
#include <private/media/VideoFrame.h>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
#include <android-base/macros.h> // for FALLTHROUGH_INTENDED
using namespace android;
static const char* const kClassMediaScannerClient =
"android/media/MediaScannerClient";
static const char* const kClassMediaScanner =
"android/media/MediaScanner";
static const char* const kRunTimeException =
"java/lang/RuntimeException";
static const char* const kIllegalArgumentException =
"java/lang/IllegalArgumentException";
struct fields_t {
jfieldID context;
};
static fields_t fields;
static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
return UNKNOWN_ERROR;
}
return OK;
}
// stolen from dalvik/vm/checkJni.cpp
static bool isValidUtf8(const char* bytes) {
while (*bytes != '\0') {
unsigned char utf8 = *(bytes++);
// Switch on the high four bits.
switch (utf8 >> 4) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
// Bit pattern 0xxx. No need for any extra bytes.
break;
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
case 0x0f:
/*
* Bit pattern 10xx or 1111, which are illegal start bytes.
* Note: 1111 is valid for normal UTF-8, but not the
* modified UTF-8 used here.
*/
return false;
case 0x0e:
// Bit pattern 1110, so there are two additional bytes.
utf8 = *(bytes++);
if ((utf8 & 0xc0) != 0x80) {
return false;
}
// Fall through to take care of the final byte.
FALLTHROUGH_INTENDED;
case 0x0c:
case 0x0d:
// Bit pattern 110x, so there is one additional byte.
utf8 = *(bytes++);
if ((utf8 & 0xc0) != 0x80) {
return false;
}
break;
}
}
return true;
}
class MyMediaScannerClient : public MediaScannerClient
{
public:
MyMediaScannerClient(JNIEnv *env, jobject client)
: mEnv(env),
mClient(env->NewGlobalRef(client)),
mScanFileMethodID(0),
mHandleStringTagMethodID(0),
mSetMimeTypeMethodID(0)
{
ALOGV("MyMediaScannerClient constructor");
jclass mediaScannerClientInterface =
env->FindClass(kClassMediaScannerClient);
if (mediaScannerClientInterface == NULL) {
ALOGE("Class %s not found", kClassMediaScannerClient);
} else {
mScanFileMethodID = env->GetMethodID(
mediaScannerClientInterface,
"scanFile",
"(Ljava/lang/String;JJZZ)V");
mHandleStringTagMethodID = env->GetMethodID(
mediaScannerClientInterface,
"handleStringTag",
"(Ljava/lang/String;Ljava/lang/String;)V");
mSetMimeTypeMethodID = env->GetMethodID(
mediaScannerClientInterface,
"setMimeType",
"(Ljava/lang/String;)V");
}
}
virtual ~MyMediaScannerClient()
{
ALOGV("MyMediaScannerClient destructor");
mEnv->DeleteGlobalRef(mClient);
}
virtual status_t scanFile(const char* path, long long lastModified,
long long fileSize, bool isDirectory, bool noMedia)
{
ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
path, lastModified, fileSize, isDirectory);
jstring pathStr;
if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
mEnv->ExceptionClear();
return NO_MEMORY;
}
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
fileSize, isDirectory, noMedia);
mEnv->DeleteLocalRef(pathStr);
return checkAndClearExceptionFromCallback(mEnv, "scanFile");
}
virtual status_t handleStringTag(const char* name, const char* value)
{
ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
jstring nameStr, valueStr;
if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
mEnv->ExceptionClear();
return NO_MEMORY;
}
char *cleaned = NULL;
if (!isValidUtf8(value)) {
cleaned = strdup(value);
char *chp = cleaned;
char ch;
while ((ch = *chp)) {
if (ch & 0x80) {
*chp = '?';
}
chp++;
}
value = cleaned;
}
valueStr = mEnv->NewStringUTF(value);
free(cleaned);
if (valueStr == NULL) {
mEnv->DeleteLocalRef(nameStr);
mEnv->ExceptionClear();
return NO_MEMORY;
}
mEnv->CallVoidMethod(
mClient, mHandleStringTagMethodID, nameStr, valueStr);
mEnv->DeleteLocalRef(nameStr);
mEnv->DeleteLocalRef(valueStr);
return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
}
virtual status_t setMimeType(const char* mimeType)
{
ALOGV("setMimeType: %s", mimeType);
jstring mimeTypeStr;
if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
mEnv->ExceptionClear();
return NO_MEMORY;
}
mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
mEnv->DeleteLocalRef(mimeTypeStr);
return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
}
private:
JNIEnv *mEnv;
jobject mClient;
jmethodID mScanFileMethodID;
jmethodID mHandleStringTagMethodID;
jmethodID mSetMimeTypeMethodID;
};
static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
{
return (MediaScanner *) env->GetLongField(thiz, fields.context);
}
static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
{
env->SetLongField(thiz, fields.context, (jlong)s);
}
static void
android_media_MediaScanner_processDirectory(
JNIEnv *env, jobject thiz, jstring path, jobject client)
{
ALOGV("processDirectory");
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return;
}
if (path == NULL) {
jniThrowException(env, kIllegalArgumentException, NULL);
return;
}
const char *pathStr = env->GetStringUTFChars(path, NULL);
if (pathStr == NULL) { // Out of memory
return;
}
MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processDirectory(pathStr, myClient);
if (result == MEDIA_SCAN_RESULT_ERROR) {
ALOGE("An error occurred while scanning directory '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
}
static jboolean
android_media_MediaScanner_processFile(
JNIEnv *env, jobject thiz, jstring path,
jstring mimeType, jobject client)
{
ALOGV("processFile");
// Lock already hold by processDirectory
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return false;
}
if (path == NULL) {
jniThrowException(env, kIllegalArgumentException, NULL);
return false;
}
const char *pathStr = env->GetStringUTFChars(path, NULL);
if (pathStr == NULL) { // Out of memory
return false;
}
const char *mimeTypeStr =
(mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
if (mimeType && mimeTypeStr == NULL) { // Out of memory
// ReleaseStringUTFChars can be called with an exception pending.
env->ReleaseStringUTFChars(path, pathStr);
return false;
}
MyMediaScannerClient myClient(env, client);
MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
if (result == MEDIA_SCAN_RESULT_ERROR) {
ALOGE("An error occurred while scanning file '%s'.", pathStr);
}
env->ReleaseStringUTFChars(path, pathStr);
if (mimeType) {
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
return result != MEDIA_SCAN_RESULT_ERROR;
}
static void
android_media_MediaScanner_setLocale(
JNIEnv *env, jobject thiz, jstring locale)
{
ALOGV("setLocale");
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return;
}
if (locale == NULL) {
jniThrowException(env, kIllegalArgumentException, NULL);
return;
}
const char *localeStr = env->GetStringUTFChars(locale, NULL);
if (localeStr == NULL) { // Out of memory
return;
}
mp->setLocale(localeStr);
env->ReleaseStringUTFChars(locale, localeStr);
}
static jbyteArray
android_media_MediaScanner_extractAlbumArt(
JNIEnv *env, jobject thiz, jobject fileDescriptor)
{
ALOGV("extractAlbumArt");
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "No scanner available");
return NULL;
}
if (fileDescriptor == NULL) {
jniThrowException(env, kIllegalArgumentException, NULL);
return NULL;
}
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
if (mediaAlbumArt == NULL) {
return NULL;
}
jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
if (array != NULL) {
const jbyte* data =
reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
}
free(mediaAlbumArt);
// if NewByteArray() returned NULL, an out-of-memory
// exception will have been raised. I just want to
// return null in that case.
env->ExceptionClear();
return array;
}
// This function gets a field ID, which in turn causes class initialization.
// It is called from a static block in MediaScanner, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
ALOGV("native_init");
jclass clazz = env->FindClass(kClassMediaScanner);
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
}
static void
android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
{
ALOGV("native_setup");
MediaScanner *mp = new StagefrightMediaScanner;
if (mp == NULL) {
jniThrowException(env, kRunTimeException, "Out of memory");
return;
}
env->SetLongField(thiz, fields.context, (jlong)mp);
}
static void
android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
{
ALOGV("native_finalize");
MediaScanner *mp = getNativeScanner_l(env, thiz);
if (mp == 0) {
return;
}
delete mp;
setNativeScanner_l(env, thiz, 0);
}
static const JNINativeMethod gMethods[] = {
{
"processDirectory",
"(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void *)android_media_MediaScanner_processDirectory
},
{
"processFile",
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
(void *)android_media_MediaScanner_processFile
},
{
"setLocale",
"(Ljava/lang/String;)V",
(void *)android_media_MediaScanner_setLocale
},
{
"extractAlbumArt",
"(Ljava/io/FileDescriptor;)[B",
(void *)android_media_MediaScanner_extractAlbumArt
},
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
{
"native_setup",
"()V",
(void *)android_media_MediaScanner_native_setup
},
{
"native_finalize",
"()V",
(void *)android_media_MediaScanner_native_finalize
},
};
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));
}