Change-Id: I9ee62d2463d8df1246a84774e8ac7e674778279a Signed-off-by: Mike Lockwood <lockwood@android.com>
439 lines
15 KiB
C++
439 lines
15 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_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 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::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_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));
|
|
}
|