android_frameworks_base/media/jni/android_media_MtpDatabase.cpp
Mike Lockwood d21eac9c70 MTP: Use media provider database to implement MTP device support.
Uses a new "MTP objects" table in the media provider to support basic
enumeration of the external storage file system.
Support for accessing audio, video and image metadata in the existing
media provider tables will be added in a later commit.

The C++ MtpDatabase class is now abstract, to support a proxy subclass that
calls through JNI to the Java MtpDatabase class in the media provider.

Change-Id: I90f0db5f3acc5d35ae78c27a8507edff16d14305
Signed-off-by: Mike Lockwood <lockwood@android.com>
2010-07-08 16:21:09 -04:00

452 lines
16 KiB
C++

/*
* Copyright (C) 2010 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_TAG "MtpDatabaseJNI"
#include "utils/Log.h"
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "MtpDatabase.h"
#include "MtpDataPacket.h"
#include "MtpUtils.h"
#include "mtp.h"
using namespace android;
// ----------------------------------------------------------------------------
static jmethodID method_getObjectHandle;
static jmethodID method_addFile;
static jmethodID method_getObjectList;
static jmethodID method_getObjectProperty;
static jmethodID method_getObjectInfo;
static jmethodID method_getObjectFilePath;
static jmethodID method_deleteFile;
static jfieldID field_context;
MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
return (MtpDatabase *)env->GetIntField(database, field_context);
}
// ----------------------------------------------------------------------------
class MyMtpDatabase : public MtpDatabase {
private:
jobject mDatabase;
jintArray mIntBuffer;
jlongArray mLongBuffer;
jcharArray mStringBuffer;
public:
MyMtpDatabase(JNIEnv *env, jobject client);
virtual ~MyMtpDatabase();
void cleanup(JNIEnv *env);
virtual MtpObjectHandle getObjectHandle(const char* path);
virtual MtpObjectHandle addFile(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
MtpStorageID storage,
uint64_t size,
time_t modified);
virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID,
MtpObjectFormat format,
MtpObjectHandle parent);
virtual MtpResponseCode getObjectProperty(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet);
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
MtpDataPacket& packet);
virtual bool getObjectFilePath(MtpObjectHandle handle,
MtpString& filePath,
int64_t& fileLength);
virtual bool deleteFile(MtpObjectHandle handle);
// helper for media scanner
virtual MtpObjectHandle* getFileList(int& outCount);
virtual void beginTransaction();
virtual void commitTransaction();
virtual void rollbackTransaction();
bool getPropertyInfo(MtpObjectProperty property, int& type);
};
MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
: mDatabase(env->NewGlobalRef(client)),
mIntBuffer(NULL),
mLongBuffer(NULL),
mStringBuffer(NULL)
{
jintArray intArray;
jlongArray longArray;
jcharArray charArray;
// create buffers for out arguments
// we don't need to be thread-safe so this is OK
intArray = env->NewIntArray(3);
if (!intArray)
goto out_of_memory;
mIntBuffer = (jintArray)env->NewGlobalRef(intArray);
longArray = env->NewLongArray(2);
if (!longArray)
goto out_of_memory;
mLongBuffer = (jlongArray)env->NewGlobalRef(longArray);
charArray = env->NewCharArray(256);
if (!charArray)
goto out_of_memory;
mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
return;
out_of_memory:
env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), NULL);
}
void MyMtpDatabase::cleanup(JNIEnv *env) {
env->DeleteGlobalRef(mDatabase);
env->DeleteGlobalRef(mIntBuffer);
env->DeleteGlobalRef(mLongBuffer);
env->DeleteGlobalRef(mStringBuffer);
}
MyMtpDatabase::~MyMtpDatabase() {
}
MtpObjectHandle MyMtpDatabase::getObjectHandle(const char* path) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallIntMethod(mDatabase, method_getObjectHandle, env->NewStringUTF(path));
}
MtpObjectHandle MyMtpDatabase::addFile(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
MtpStorageID storage,
uint64_t size,
time_t modified) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallIntMethod(mDatabase, method_addFile, env->NewStringUTF(path),
(jint)format, (jint)parent, (jint)storage, (jlong)size, (jlong)modified);
}
MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID,
MtpObjectFormat format,
MtpObjectHandle parent) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectList,
(jint)storageID, (jint)format, (jint)parent);
if (!array)
return NULL;
MtpObjectHandleList* list = new MtpObjectHandleList();
jint* handles = env->GetIntArrayElements(array, 0);
jsize length = env->GetArrayLength(array);
LOGD("getObjectList length: %d", length);
for (int i = 0; i < length; i++) {
LOGD("push: %d", handles[i]);
list->push(handles[i]);
}
env->ReleaseIntArrayElements(array, handles, 0);
return list;
}
MtpResponseCode MyMtpDatabase::getObjectProperty(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet) {
int type;
if (!getPropertyInfo(property, type))
return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
JNIEnv* env = AndroidRuntime::getJNIEnv();
jint result = env->CallIntMethod(mDatabase, method_getObjectProperty,
(jint)handle, (jint)property, mLongBuffer, mStringBuffer);
if (result != MTP_RESPONSE_OK)
return result;
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
jlong longValue = longValues[0];
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
switch (type) {
case MTP_TYPE_INT8:
packet.putInt8(longValue);
break;
case MTP_TYPE_UINT8:
packet.putUInt8(longValue);
break;
case MTP_TYPE_INT16:
packet.putInt16(longValue);
break;
case MTP_TYPE_UINT16:
packet.putUInt16(longValue);
break;
case MTP_TYPE_INT32:
packet.putInt32(longValue);
break;
case MTP_TYPE_UINT32:
packet.putUInt32(longValue);
break;
case MTP_TYPE_INT64:
packet.putInt64(longValue);
break;
case MTP_TYPE_UINT64:
packet.putUInt64(longValue);
break;
case MTP_TYPE_STR:
{
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
packet.putString(str);
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
break;
}
default:
LOGE("unsupported object type\n");
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
}
return MTP_RESPONSE_OK;
}
MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
MtpDataPacket& packet) {
char date[20];
JNIEnv* env = AndroidRuntime::getJNIEnv();
jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectInfo,
(jint)handle, mIntBuffer, mStringBuffer, mLongBuffer);
if (!result)
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
MtpStorageID storageID = intValues[0];
MtpObjectFormat format = intValues[1];
MtpObjectHandle parent = intValues[2];
env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
uint64_t size = longValues[0];
uint64_t modified = longValues[1];
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
int associationType = (format == MTP_FORMAT_ASSOCIATION ?
MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
MTP_ASSOCIATION_TYPE_UNDEFINED);
packet.putUInt32(storageID);
packet.putUInt16(format);
packet.putUInt16(0); // protection status
packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
packet.putUInt16(0); // thumb format
packet.putUInt32(0); // thumb compressed size
packet.putUInt32(0); // thumb pix width
packet.putUInt32(0); // thumb pix height
packet.putUInt32(0); // image pix width
packet.putUInt32(0); // image pix height
packet.putUInt32(0); // image bit depth
packet.putUInt32(parent);
packet.putUInt16(associationType);
packet.putUInt32(0); // association desc
packet.putUInt32(0); // sequence number
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
packet.putString(str); // file name
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
packet.putEmptyString();
formatDateTime(modified, date, sizeof(date));
packet.putString(date); // date modified
packet.putEmptyString(); // keywords
return MTP_RESPONSE_OK;
}
bool MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
MtpString& filePath,
int64_t& fileLength) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jboolean result = env->CallBooleanMethod(mDatabase, method_getObjectFilePath,
(jint)handle, mStringBuffer, mLongBuffer);
if (!result)
return false;
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
filePath.setTo(str, strlen16(str));
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
fileLength = longValues[0];
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
return true;
}
bool MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
return env->CallBooleanMethod(mDatabase, method_deleteFile, (jint)handle);
}
// helper for media scanner
MtpObjectHandle* MyMtpDatabase::getFileList(int& outCount) {
// REMOVE ME
return NULL;
}
void MyMtpDatabase::beginTransaction() {
// REMOVE ME
}
void MyMtpDatabase::commitTransaction() {
// REMOVE ME
}
void MyMtpDatabase::rollbackTransaction() {
// REMOVE ME
}
struct PropertyTableEntry {
MtpObjectProperty property;
int type;
};
static const PropertyTableEntry kPropertyTable[] = {
{ MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
{ MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
{ MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT32 },
{ MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR },
{ MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 },
{ MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR },
};
bool MyMtpDatabase::getPropertyInfo(MtpObjectProperty property, int& type) {
int count = sizeof(kPropertyTable) / sizeof(kPropertyTable[0]);
const PropertyTableEntry* entry = kPropertyTable;
for (int i = 0; i < count; i++, entry++) {
if (entry->property == property) {
type = entry->type;
return true;
}
}
return false;
}
// ----------------------------------------------------------------------------
static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
if (env->ExceptionCheck()) {
LOGE("An exception was thrown by callback '%s'.", methodName);
LOGE_EX(env);
env->ExceptionClear();
}
}
// ----------------------------------------------------------------------------
static void
android_media_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{
LOGD("setup\n");
MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
env->SetIntField(thiz, field_context, (int)database);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
static void
android_media_MtpDatabase_finalize(JNIEnv *env, jobject thiz)
{
LOGD("finalize\n");
MyMtpDatabase* database = (MyMtpDatabase *)env->GetIntField(thiz, field_context);
database->cleanup(env);
delete database;
env->SetIntField(thiz, field_context, 0);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
{"native_setup", "()V", (void *)android_media_MtpDatabase_setup},
{"native_finalize", "()V", (void *)android_media_MtpDatabase_finalize},
};
static const char* const kClassPathName = "android/media/MtpDatabase";
int register_android_media_MtpDatabase(JNIEnv *env)
{
jclass clazz;
LOGD("register_android_media_MtpDatabase\n");
clazz = env->FindClass("android/media/MtpDatabase");
if (clazz == NULL) {
LOGE("Can't find android/media/MtpDatabase");
return -1;
}
method_getObjectHandle = env->GetMethodID(clazz, "getObjectHandle", "(Ljava/lang/String;)I");
if (method_getObjectHandle == NULL) {
LOGE("Can't find getObjectHandle");
return -1;
}
method_addFile = env->GetMethodID(clazz, "addFile", "(Ljava/lang/String;IIIJJ)I");
if (method_addFile == NULL) {
LOGE("Can't find addFile");
return -1;
}
method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I");
if (method_getObjectList == NULL) {
LOGE("Can't find getObjectList");
return -1;
}
method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I");
if (method_getObjectProperty == NULL) {
LOGE("Can't find getObjectProperty");
return -1;
}
method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
if (method_getObjectInfo == NULL) {
LOGE("Can't find getObjectInfo");
return -1;
}
method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)Z");
if (method_getObjectFilePath == NULL) {
LOGE("Can't find getObjectFilePath");
return -1;
}
method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)Z");
if (method_deleteFile == NULL) {
LOGE("Can't find deleteFile");
return -1;
}
field_context = env->GetFieldID(clazz, "mNativeContext", "I");
if (field_context == NULL) {
LOGE("Can't find MtpDatabase.mNativeContext");
return -1;
}
return AndroidRuntime::registerNativeMethods(env,
"android/media/MtpDatabase", gMethods, NELEM(gMethods));
}