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>
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 9a3c618..2c4a9f5 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -239,6 +239,44 @@
         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 6e527d9..0decb1d 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -20,6 +20,7 @@
 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 @@
         }
     }
     
-    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 @@
     }
 
     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 @@
         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 @@
         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 0000000..0869530
--- /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 a9a54e7..766a86a 100644
--- a/media/java/android/media/MtpServer.java
+++ b/media/java/android/media/MtpServer.java
@@ -30,8 +30,8 @@
         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 @@
     // 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 3a7291f..6fe3c3b 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -15,6 +15,7 @@
     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 47f1974..5c2ec00 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -766,6 +766,7 @@
 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 @@
         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 0000000..86d1fc4
--- /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 e6a3835..6a1c64b 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 @@
 
 static jfieldID field_context;
 
+// in android_media_MtpDatabase.cpp
+extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
 
 // ----------------------------------------------------------------------------
 
@@ -47,14 +50,13 @@
 
 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 @@
             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 @@
 };
 
 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 @@
 // ----------------------------------------------------------------------------
 
 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 a7e975c..6f9ea24 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -299,17 +299,28 @@
         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 146ef64..759c0f9 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -79,6 +79,7 @@
     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 3456815..967ebc9 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -113,11 +113,10 @@
     // 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 @@
         mSendObjectHandle(kInvalidObjectHandle),
         mSendObjectFileSize(0)
 {
-    mDatabase = new MtpSqliteDatabase();
-    mDatabase->open(databasePath, true);
-
     initObjectProperties();
 }
 
@@ -427,6 +423,8 @@
     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 @@
         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 @@
     }
 
     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 25635af..09556b3 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 @@
     // 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 @@
     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 c11ba50..eae9cb8 100644
--- a/media/mtp/MtpSqliteDatabase.cpp
+++ b/media/mtp/MtpSqliteDatabase.cpp
@@ -414,7 +414,6 @@
     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 af0f77f..a2cb826 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 @@
     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();