/* * 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 #include #include #include #include #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)); }