diff options
| -rw-r--r-- | core/java/android/provider/MediaStore.java | 38 | ||||
| -rw-r--r-- | media/java/android/media/MediaFile.java | 83 | ||||
| -rw-r--r-- | media/java/android/media/MtpDatabase.java | 223 | ||||
| -rw-r--r-- | media/java/android/media/MtpServer.java | 6 | ||||
| -rw-r--r-- | media/jni/Android.mk | 1 | ||||
| -rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 6 | ||||
| -rw-r--r-- | media/jni/android_media_MtpDatabase.cpp | 451 | ||||
| -rw-r--r-- | media/jni/android_media_MtpServer.cpp | 24 | ||||
| -rw-r--r-- | media/mtp/MtpDataPacket.cpp | 19 | ||||
| -rw-r--r-- | media/mtp/MtpDataPacket.h | 1 | ||||
| -rw-r--r-- | media/mtp/MtpServer.cpp | 17 | ||||
| -rw-r--r-- | media/mtp/MtpServer.h | 11 | ||||
| -rw-r--r-- | media/mtp/MtpSqliteDatabase.cpp | 1 | ||||
| -rw-r--r-- | media/mtp/mtptest.cpp | 5 |
14 files changed, 823 insertions, 63 deletions
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 9a3c6180bedb..2c4a9f5359de 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -239,6 +239,44 @@ public final class MediaStore { public static final String MIME_TYPE = "mime_type"; } + + + /** + * Media provider interface used by MTP implementation. + * @hide + */ + public static final class MtpObjects { + + public static Uri getContentUri(String volumeName) { + return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + + "/object"); + } + + public static final Uri getContentUri(String volumeName, + long objectId) { + return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + + "/object/" + objectId); + } + + /** + * Fields for master table for all media files. + * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED. + */ + public interface ObjectColumns extends MediaColumns { + /** + * The MTP format code of the file + * <P>Type: INTEGER</P> + */ + public static final String FORMAT = "format"; + + /** + * The index of the parent directory of the file + * <P>Type: INTEGER</P> + */ + public static final String PARENT = "parent"; + } + } + /** * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended * to be accessed elsewhere. diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 6e527d9b1649..0decb1d0b317 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; +import android.provider.Mtp; import android.media.DecoderCapabilities; import android.media.DecoderCapabilities.VideoDecoder; import android.media.DecoderCapabilities.AudioDecoder; @@ -96,15 +97,28 @@ public class MediaFile { } } - private static HashMap<String, MediaFileType> sFileTypeMap + private static HashMap<String, MediaFileType> sFileTypeMap = new HashMap<String, MediaFileType>(); - private static HashMap<String, Integer> sMimeTypeMap - = new HashMap<String, Integer>(); + private static HashMap<String, Integer> sMimeTypeMap + = new HashMap<String, Integer>(); + // maps file extension to MTP format code + private static HashMap<String, Integer> sFileTypeToFormatMap + = new HashMap<String, Integer>(); + // maps mime type to MTP format code + private static HashMap<String, Integer> sMimeTypeToFormatMap + = new HashMap<String, Integer>(); + static void addFileType(String extension, int fileType, String mimeType) { sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType)); sMimeTypeMap.put(mimeType, Integer.valueOf(fileType)); } + static void addFileType(String extension, int fileType, String mimeType, int mtpFormatCode) { + addFileType(extension, fileType, mimeType); + sFileTypeToFormatMap.put(extension, Integer.valueOf(mtpFormatCode)); + sMimeTypeToFormatMap.put(mimeType, Integer.valueOf(mtpFormatCode)); + } + private static boolean isWMAEnabled() { List<AudioDecoder> decoders = DecoderCapabilities.getAudioDecoders(); for (AudioDecoder decoder: decoders) { @@ -126,17 +140,17 @@ public class MediaFile { } static { - addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg"); - addFileType("M4A", FILE_TYPE_M4A, "audio/mp4"); - addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav"); + addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg", Mtp.Object.FORMAT_MP3); + addFileType("M4A", FILE_TYPE_M4A, "audio/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav", Mtp.Object.FORMAT_WAV); addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); if (isWMAEnabled()) { - addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma"); + addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", Mtp.Object.FORMAT_WMA); } - addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); - addFileType("OGA", FILE_TYPE_OGG, "application/ogg"); - addFileType("AAC", FILE_TYPE_AAC, "audio/aac"); + addFileType("OGG", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG); + addFileType("OGA", FILE_TYPE_OGG, "application/ogg", Mtp.Object.FORMAT_OGG); + addFileType("AAC", FILE_TYPE_AAC, "audio/aac", Mtp.Object.FORMAT_AAC); addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); addFileType("MID", FILE_TYPE_MID, "audio/midi"); @@ -148,32 +162,32 @@ public class MediaFile { addFileType("RTX", FILE_TYPE_MID, "audio/midi"); addFileType("OTA", FILE_TYPE_MID, "audio/midi"); - addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg"); - addFileType("MP4", FILE_TYPE_MP4, "video/mp4"); - addFileType("M4V", FILE_TYPE_M4V, "video/mp4"); - addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); - addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); + addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg", Mtp.Object.FORMAT_MPEG); + addFileType("MP4", FILE_TYPE_MP4, "video/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("M4V", FILE_TYPE_M4V, "video/mp4", Mtp.Object.FORMAT_MPEG); + addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER); + addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2", Mtp.Object.FORMAT_3GP_CONTAINER); addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); if (isWMVEnabled()) { - addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); + addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv", Mtp.Object.FORMAT_WMV); addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); } - addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("GIF", FILE_TYPE_GIF, "image/gif"); - addFileType("PNG", FILE_TYPE_PNG, "image/png"); - addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp"); + addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG); + addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg", Mtp.Object.FORMAT_EXIF_JPEG); + addFileType("GIF", FILE_TYPE_GIF, "image/gif", Mtp.Object.FORMAT_GIF); + addFileType("PNG", FILE_TYPE_PNG, "image/png", Mtp.Object.FORMAT_PNG); + addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", Mtp.Object.FORMAT_BMP); addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp"); - addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl"); - addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls"); - addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl"); + addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", Mtp.Object.FORMAT_M3U_PLAYLIST); + addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls", Mtp.Object.FORMAT_PLS_PLAYLIST); + addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl", Mtp.Object.FORMAT_WPL_PLAYLIST); // compute file extensions list for native Media Scanner StringBuilder builder = new StringBuilder(); @@ -222,4 +236,21 @@ public class MediaFile { return (value == null ? 0 : value.intValue()); } + public static int getFormatCode(String fileName, String mimeType) { + if (mimeType != null) { + Integer value = sMimeTypeToFormatMap.get(mimeType); + if (value != null) { + return value.intValue(); + } + } + int lastDot = fileName.lastIndexOf('.'); + if (lastDot > 0) { + String extension = fileName.substring(lastDot + 1); + Integer value = sFileTypeToFormatMap.get(extension); + if (value != null) { + return value.intValue(); + } + } + return Mtp.Object.FORMAT_UNDEFINED; + } } diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java new file mode 100644 index 000000000000..0869530a6928 --- /dev/null +++ b/media/java/android/media/MtpDatabase.java @@ -0,0 +1,223 @@ +/* + * 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. + */ + +package android.media; + +import android.content.Context; +import android.content.IContentProvider; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.MediaStore.MtpObjects; +import android.util.Log; + +/** + * {@hide} + */ +public class MtpDatabase { + + private static final String TAG = "MtpDatabase"; + + private final IContentProvider mMediaProvider; + private final String mVolumeName; + private final Uri mObjectsUri; + + private static final String[] ID_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + }; + private static final String[] PATH_SIZE_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + MtpObjects.ObjectColumns.DATA, // 1 + MtpObjects.ObjectColumns.SIZE, // 2 + }; + private static final String[] OBJECT_INFO_PROJECTION = new String[] { + MtpObjects.ObjectColumns._ID, // 0 + MtpObjects.ObjectColumns.DATA, // 1 + MtpObjects.ObjectColumns.FORMAT, // 2 + MtpObjects.ObjectColumns.PARENT, // 3 + MtpObjects.ObjectColumns.SIZE, // 4 + MtpObjects.ObjectColumns.DATE_MODIFIED, // 5 + }; + private static final String ID_WHERE = MtpObjects.ObjectColumns._ID + "=?"; + private static final String PATH_WHERE = MtpObjects.ObjectColumns.DATA + "=?"; + private static final String PARENT_WHERE = MtpObjects.ObjectColumns.PARENT + "=?"; + private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + + MtpObjects.ObjectColumns.FORMAT + "=?"; + + static { + System.loadLibrary("media_jni"); + } + + public MtpDatabase(Context context, String volumeName) { + native_setup(); + + mMediaProvider = context.getContentResolver().acquireProvider("media"); + mVolumeName = volumeName; + mObjectsUri = MtpObjects.getContentUri(volumeName); + } + + @Override + protected void finalize() { + native_finalize(); + } + + // called from native code + private int getObjectHandle(String path) { + Log.d(TAG, "getObjectHandle " + path); + Cursor c = null; + try { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PATH_WHERE, new String[] { path }, null); + if (c != null && c.moveToNext()) { + return c.getInt(0); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectHandle", e); + } finally { + if (c != null) { + c.close(); + } + } + return 0; + } + + private int addFile(String path, int format, int parent, + int storage, long size, long modified) { + Log.d(TAG, "addFile " + path); + return 0; + } + + private int[] getObjectList(int storageID, int format, int parent) { + // we can ignore storageID until we support multiple storages + Log.d(TAG, "getObjectList parent: " + parent); + Cursor c = null; + try { + if (format != 0) { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_FORMAT_WHERE, + new String[] { Integer.toString(parent), Integer.toString(format) }, + null); + } else { + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, + PARENT_WHERE, new String[] { Integer.toString(parent) }, null); + } + if (c == null) { + Log.d(TAG, "null cursor"); + return null; + } + int count = c.getCount(); + if (count > 0) { + int[] result = new int[count]; + for (int i = 0; i < count; i++) { + c.moveToNext(); + result[i] = c.getInt(0); + } + Log.d(TAG, "returning " + result); + return result; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectList", e); + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + + private int getObjectProperty(int handle, int property, + long[] outIntValue, char[] outStringValue) { + Log.d(TAG, "getObjectProperty: " + property); + return 0; + } + + private boolean getObjectInfo(int handle, int[] outStorageFormatParent, + char[] outName, long[] outSizeModified) { + Log.d(TAG, "getObjectInfo: " + handle); + Cursor c = null; + try { + c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, + ID_WHERE, new String[] { Integer.toString(handle) }, null); + if (c != null && c.moveToNext()) { + outStorageFormatParent[0] = 0x00010001; + outStorageFormatParent[1] = c.getInt(2); + outStorageFormatParent[2] = c.getInt(3); + + // extract name from path + String path = c.getString(1); + int lastSlash = path.lastIndexOf('/'); + int start = (lastSlash >= 0 ? lastSlash + 1 : 0); + int end = path.length(); + if (end - start > 255) { + end = start + 255; + } + path.getChars(start, end, outName, 0); + outName[end - start] = 0; + + outSizeModified[0] = c.getLong(4); + outSizeModified[1] = c.getLong(5); + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectProperty", e); + } finally { + if (c != null) { + c.close(); + } + } + return false; + } + + private boolean getObjectFilePath(int handle, char[] outFilePath, long[] outFileLength) { + Log.d(TAG, "getObjectFilePath: " + handle); + Cursor c = null; + try { + c = mMediaProvider.query(mObjectsUri, PATH_SIZE_PROJECTION, + ID_WHERE, new String[] { Integer.toString(handle) }, null); + if (c != null && c.moveToNext()) { + String path = c.getString(1); + path.getChars(0, path.length(), outFilePath, 0); + outFilePath[path.length()] = 0; + outFileLength[0] = c.getLong(2); + return true; + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectFilePath", e); + } finally { + if (c != null) { + c.close(); + } + } + return false; + } + + private boolean deleteFile(int handle) { + Log.d(TAG, "deleteFile: " + handle); + Uri uri = MtpObjects.getContentUri(mVolumeName, handle); + try { + return (mMediaProvider.delete(uri, null, null) == 1); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in deleteFile", e); + return false; + } + } + + // used by the JNI code + private int mNativeContext; + + private native final void native_setup(); + private native final void native_finalize(); +} diff --git a/media/java/android/media/MtpServer.java b/media/java/android/media/MtpServer.java index a9a54e7748cd..766a86a5b54e 100644 --- a/media/java/android/media/MtpServer.java +++ b/media/java/android/media/MtpServer.java @@ -30,8 +30,8 @@ public class MtpServer { System.loadLibrary("media_jni"); } - public MtpServer(String storagePath, String databasePath) { - native_setup(storagePath, databasePath); + public MtpServer(MtpDatabase database, String storagePath) { + native_setup(database, storagePath); } @Override @@ -50,7 +50,7 @@ public class MtpServer { // used by the JNI code private int mNativeContext; - private native final void native_setup(String storagePath, String databasePath); + private native final void native_setup(MtpDatabase database, String storagePath); private native final void native_finalize(); private native final void native_start(); private native final void native_stop(); diff --git a/media/jni/Android.mk b/media/jni/Android.mk index 3a7291f26046..6fe3c3b8bd76 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -15,6 +15,7 @@ LOCAL_SRC_FILES:= \ android_media_AmrInputStream.cpp \ android_media_MtpClient.cpp \ android_media_MtpCursor.cpp \ + android_media_MtpDatabase.cpp \ android_media_MtpServer.cpp \ LOCAL_SHARED_LIBRARIES := \ diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 47f1974017af..5c2ec0066697 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -766,6 +766,7 @@ extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); extern int register_android_media_MtpClient(JNIEnv *env); extern int register_android_media_MtpCursor(JNIEnv *env); +extern int register_android_media_MtpDatabase(JNIEnv *env); extern int register_android_media_MtpServer(JNIEnv *env); #ifndef NO_OPENCORE @@ -830,6 +831,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_MtpDatabase(env) < 0) { + LOGE("ERROR: MtpDatabase native registration failed"); + goto bail; + } + if (register_android_media_MtpServer(env) < 0) { LOGE("ERROR: MtpServer native registration failed"); goto bail; diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp new file mode 100644 index 000000000000..86d1fc45b0c7 --- /dev/null +++ b/media/jni/android_media_MtpDatabase.cpp @@ -0,0 +1,451 @@ +/* + * 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)); +} diff --git a/media/jni/android_media_MtpServer.cpp b/media/jni/android_media_MtpServer.cpp index e6a3835a9e4d..6a1c64b0dca9 100644 --- a/media/jni/android_media_MtpServer.cpp +++ b/media/jni/android_media_MtpServer.cpp @@ -30,6 +30,7 @@ #include "private/android_filesystem_config.h" #include "MtpServer.h" +#include "MtpSqliteDatabase.h" // REMOVE using namespace android; @@ -37,6 +38,8 @@ using namespace android; static jfieldID field_context; +// in android_media_MtpDatabase.cpp +extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database); // ---------------------------------------------------------------------------- @@ -47,14 +50,13 @@ static bool ExceptionCheck(void* env) class MtpThread : public Thread { private: + MtpDatabase* mDatabase; String8 mStoragePath; - String8 mDatabasePath; bool mDone; - bool mScannedOnce; public: - MtpThread(const char* storagePath, const char* databasePath) - : mStoragePath(storagePath), mDatabasePath(databasePath), mDone(false), mScannedOnce(false) + MtpThread(MtpDatabase* database, const char* storagePath) + : mDatabase(database), mStoragePath(storagePath), mDone(false) { } @@ -66,12 +68,10 @@ public: return false; } - MtpServer* server = new MtpServer(fd, mDatabasePath, AID_SDCARD_RW, 0664, 0775); + MtpServer* server = new MtpServer(fd, mDatabase, AID_SDCARD_RW, 0664, 0775); server->addStorage(mStoragePath); // temporary - LOGD("MtpThread server->scanStorage"); - server->scanStorage(); LOGD("MtpThread server->run"); server->run(); close(fd); @@ -88,18 +88,17 @@ public: }; static void -android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jstring storagePath, jstring databasePath) +android_media_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jstring storagePath) { LOGD("setup\n"); + MtpDatabase* database = getMtpDatabase(env, javaDatabase); const char *storagePathStr = env->GetStringUTFChars(storagePath, NULL); - const char *databasePathStr = env->GetStringUTFChars(databasePath, NULL); - MtpThread* thread = new MtpThread(storagePathStr, databasePathStr); + MtpThread* thread = new MtpThread(database, storagePathStr); env->SetIntField(thiz, field_context, (int)thread); env->ReleaseStringUTFChars(storagePath, storagePathStr); - env->ReleaseStringUTFChars(databasePath, databasePathStr); } static void @@ -131,7 +130,8 @@ android_media_MtpServer_stop(JNIEnv *env, jobject thiz) // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"native_setup", "(Ljava/lang/String;Ljava/lang/String;)V", (void *)android_media_MtpServer_setup}, + {"native_setup", "(Landroid/media/MtpDatabase;Ljava/lang/String;)V", + (void *)android_media_MtpServer_setup}, {"native_finalize", "()V", (void *)android_media_MtpServer_finalize}, {"native_start", "()V", (void *)android_media_MtpServer_start}, {"native_stop", "()V", (void *)android_media_MtpServer_stop}, diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp index a7e975ca1161..6f9ea2487ee4 100644 --- a/media/mtp/MtpDataPacket.cpp +++ b/media/mtp/MtpDataPacket.cpp @@ -299,17 +299,28 @@ void MtpDataPacket::putAUInt64(const uint64_t* values, int count) { putUInt64(*values++); } -void MtpDataPacket::putString(const MtpStringBuffer& string) -{ +void MtpDataPacket::putString(const MtpStringBuffer& string) { string.writeToPacket(this); } -void MtpDataPacket::putString(const char* s) -{ +void MtpDataPacket::putString(const char* s) { MtpStringBuffer string(s); string.writeToPacket(this); } +void MtpDataPacket::putString(const uint16_t* string) { + int count = 0; + for (int i = 0; i < 256; i++) { + if (string[i]) + count++; + else + break; + } + putUInt8(count); + for (int i = 0; i < count; i++) + putUInt16(string[i]); +} + #ifdef MTP_DEVICE int MtpDataPacket::read(int fd) { // first read the header diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h index 146ef642695e..759c0f9e0aa5 100644 --- a/media/mtp/MtpDataPacket.h +++ b/media/mtp/MtpDataPacket.h @@ -79,6 +79,7 @@ public: void putAUInt64(const uint64_t* values, int count); void putString(const MtpStringBuffer& string); void putString(const char* string); + void putString(const uint16_t* string); inline void putEmptyString() { putUInt16(0); } inline void putEmptyArray() { putUInt32(0); } diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 3456815ce43b..967ebc9c02bd 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -113,11 +113,10 @@ static const MtpObjectFormat kSupportedPlaybackFormats[] = { // MTP_FORMAT_PLS_PLAYLIST, }; -MtpServer::MtpServer(int fd, const char* databasePath, +MtpServer::MtpServer(int fd, MtpDatabase* database, int fileGroup, int filePerm, int directoryPerm) : mFD(fd), - mDatabasePath(databasePath), - mDatabase(NULL), + mDatabase(database), mFileGroup(fileGroup), mFilePermission(filePerm), mDirectoryPermission(directoryPerm), @@ -126,9 +125,6 @@ MtpServer::MtpServer(int fd, const char* databasePath, mSendObjectHandle(kInvalidObjectHandle), mSendObjectFileSize(0) { - mDatabase = new MtpSqliteDatabase(); - mDatabase->open(databasePath, true); - initObjectProperties(); } @@ -427,6 +423,8 @@ MtpResponseCode MtpServer::doGetObjectHandles() { MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent // 0x00000000 for all objects? + if (parent == 0xFFFFFFFF) + parent = 0; MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); mData.putAUInt32(handles); @@ -488,9 +486,10 @@ MtpResponseCode MtpServer::doSendObjectInfo() { return MTP_RESPONSE_INVALID_STORAGE_ID; // special case the root - if (parent == MTP_PARENT_ROOT) + if (parent == MTP_PARENT_ROOT) { path = storage->getPath(); - else { + parent = 0; + } else { int64_t dummy; if (!mDatabase->getObjectFilePath(parent, path, dummy)) return MTP_RESPONSE_INVALID_OBJECT_HANDLE; @@ -549,7 +548,7 @@ MtpResponseCode MtpServer::doSendObjectInfo() { } mResponse.setParameter(1, storageID); - mResponse.setParameter(2, parent); + mResponse.setParameter(2, (parent == 0 ? 0xFFFFFFFF: parent)); mResponse.setParameter(3, handle); return MTP_RESPONSE_OK; diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index 25635afbe0ea..09556b37452f 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -26,9 +26,9 @@ namespace android { -class MtpStorage; -class MtpSqliteDatabase; +class MtpDatabase; class MtpProperty; +class MtpStorage; class MtpServer { @@ -36,10 +36,7 @@ private: // file descriptor for MTP kernel driver int mFD; - // path to our sqlite3 database - const char* mDatabasePath; - - MtpSqliteDatabase* mDatabase; + MtpDatabase* mDatabase; // group to own new files and folders int mFileGroup; @@ -67,7 +64,7 @@ private: size_t mSendObjectFileSize; public: - MtpServer(int fd, const char* databasePath, + MtpServer(int fd, MtpDatabase* database, int fileGroup, int filePerm, int directoryPerm); virtual ~MtpServer(); diff --git a/media/mtp/MtpSqliteDatabase.cpp b/media/mtp/MtpSqliteDatabase.cpp index c11ba5014afd..eae9cb8d9a64 100644 --- a/media/mtp/MtpSqliteDatabase.cpp +++ b/media/mtp/MtpSqliteDatabase.cpp @@ -414,7 +414,6 @@ MtpObjectHandle* MtpSqliteDatabase::getFileList(int& outCount) { SqliteStatement stmt(mDatabase); stmt.prepare("SELECT count(*) FROM files;"); - MtpObjectHandleList* list = new MtpObjectHandleList(); if (stmt.step()) count = stmt.getColumnInt(0); diff --git a/media/mtp/mtptest.cpp b/media/mtp/mtptest.cpp index af0f77fde057..a2cb8263040f 100644 --- a/media/mtp/mtptest.cpp +++ b/media/mtp/mtptest.cpp @@ -25,6 +25,7 @@ #include <sys/ioctl.h> #include "MtpServer.h" +#include "MtpSqliteDatabase.h" #include "MtpStorage.h" #include "f_mtp.h" #include "private/android_filesystem_config.h" @@ -77,7 +78,9 @@ int main(int argc, char* argv[]) { enable_usb_function("usb_mass_storage", false); enable_usb_function("mtp", true); - MtpServer server(fd, "/data/data/mtp/mtp.db", AID_SDCARD_RW, 0664, 0775); + MtpSqliteDatabase* database = new MtpSqliteDatabase(); + database->open("/data/data/mtp/mtp.db", true); + MtpServer server(fd, database, AID_SDCARD_RW, 0664, 0775); server.addStorage(storagePath); server.scanStorage(); server.run(); |