MTP: Partial implementation of the GetObjectPropList command

In this initial implementation we only support fetching one property at a time.
Support depth = 0 (single object) or depth = 1 (all objects in a directory)
Reimplemented GetObjectPropValue on top of GetObjectPropList, since the former
is a special case of the latter.

Change-Id: Ia76ee61741d6ee3902b5c5d9fc094cf86dfaf650
Signed-off-by: Mike Lockwood <lockwood@google.com>
diff --git a/media/java/android/media/MtpConstants.java b/media/java/android/media/MtpConstants.java
index a7d33ce..b20cbd1 100644
--- a/media/java/android/media/MtpConstants.java
+++ b/media/java/android/media/MtpConstants.java
@@ -21,6 +21,30 @@
  */
 public final class MtpConstants {
 
+// MTP Data Types
+    public static final int TYPE_UNDEFINED = 0x0000;
+    public static final int TYPE_INT8 = 0x0001;
+    public static final int TYPE_UINT8 = 0x0002;
+    public static final int TYPE_INT16 = 0x0003;
+    public static final int TYPE_UINT16 = 0x0004;
+    public static final int TYPE_INT32 = 0x0005;
+    public static final int TYPE_UINT32 = 0x0006;
+    public static final int TYPE_INT64 = 0x0007;
+    public static final int TYPE_UINT64 = 0x0008;
+    public static final int TYPE_INT128 = 0x0009;
+    public static final int TYPE_UINT128 = 0x000A;
+    public static final int TYPE_AINT8 = 0x4001;
+    public static final int TYPE_AUINT8 = 0x4002;
+    public static final int TYPE_AINT16 = 0x4003;
+    public static final int TYPE_AUINT16 = 0x4004;
+    public static final int TYPE_AINT32 = 0x4005;
+    public static final int TYPE_AUINT32 = 0x4006;
+    public static final int TYPE_AINT64 = 0x4007;
+    public static final int TYPE_AUINT64 = 0x4008;
+    public static final int TYPE_AINT128 = 0x4009;
+    public static final int TYPE_AUINT128 = 0x400A;
+    public static final int TYPE_STR = 0xFFFF;
+
 // MTP Response Codes
     public static final int RESPONSE_UNDEFINED = 0x2000;
     public static final int RESPONSE_OK = 0x2001;
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index 57ab3a1..0d09d4c 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -30,6 +30,7 @@
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Mtp;
+import android.text.format.Time;
 import android.util.Log;
 
 import java.io.File;
@@ -428,6 +429,26 @@
         }
     }
 
+    private String queryAudio(int id, String column) {
+        Cursor c = null;
+        try {
+            c = mMediaProvider.query(Audio.Media.getContentUri(mVolumeName),
+                            new String [] { Files.FileColumns._ID, column },
+                            ID_WHERE, new String[] { Integer.toString(id) }, null);
+            if (c != null && c.moveToNext()) {
+                return c.getString(1);
+            } else {
+                return "";
+            }
+        } catch (Exception e) {
+            return null;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
     private String queryGenre(int id) {
         Cursor c = null;
         try {
@@ -450,7 +471,7 @@
         }
     }
 
-    private boolean queryInt(int id, String column, long[] outValue) {
+    private Long queryLong(int id, String column) {
         Cursor c = null;
         try {
             // for now we are only reading properties from the "objects" table
@@ -458,17 +479,15 @@
                             new String [] { Files.FileColumns._ID, column },
                             ID_WHERE, new String[] { Integer.toString(id) }, null);
             if (c != null && c.moveToNext()) {
-                outValue[0] = c.getLong(1);
-                return true;
+                return new Long(c.getLong(1));
             }
-            return false;
         } catch (Exception e) {
-            return false;
         } finally {
             if (c != null) {
                 c.close();
             }
         }
+        return null;
     }
 
     private String nameFromPath(String path) {
@@ -485,6 +504,238 @@
         return path.substring(start, end);
     }
 
+    private String formatDateTime(long seconds) {
+        Time time = new Time(Time.TIMEZONE_UTC);
+        time.set(seconds * 1000);
+        String result = time.format("%Y-%m-%dT%H:%M:%SZ");
+        Log.d(TAG, "formatDateTime returning " + result);
+        return result;
+    }
+
+    private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
+                        int groupCode, int depth) {
+        // FIXME - implement group support
+        // For now we only support a single property at a time
+        if (groupCode != 0) {
+            return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
+        }
+        if (depth > 1) {
+            return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
+        }
+
+        String column = null;
+        int type = MtpConstants.TYPE_UNDEFINED;
+
+         switch (property) {
+            case MtpConstants.PROPERTY_STORAGE_ID:
+                // no query needed until we support multiple storage units
+                // for now it is always mStorageID
+                type = MtpConstants.TYPE_UINT32;
+                break;
+             case MtpConstants.PROPERTY_OBJECT_FORMAT:
+                column = Files.FileColumns.FORMAT;
+                type = MtpConstants.TYPE_UINT16;
+                break;
+            case MtpConstants.PROPERTY_PROTECTION_STATUS:
+                // protection status is always 0
+                type = MtpConstants.TYPE_UINT16;
+                break;
+            case MtpConstants.PROPERTY_OBJECT_SIZE:
+                column = Files.FileColumns.SIZE;
+                type = MtpConstants.TYPE_UINT64;
+                break;
+            case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
+                column = Files.FileColumns.DATA;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_NAME:
+                column = MediaColumns.TITLE;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_DATE_MODIFIED:
+                column = Files.FileColumns.DATE_MODIFIED;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_DATE_ADDED:
+                column = Files.FileColumns.DATE_ADDED;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
+                column = Audio.AudioColumns.YEAR;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_PARENT_OBJECT:
+                column = Files.FileColumns.PARENT;
+                type = MtpConstants.TYPE_UINT32;
+                break;
+            case MtpConstants.PROPERTY_PERSISTENT_UID:
+                // PUID is concatenation of storageID and object handle
+                type = MtpConstants.TYPE_UINT128;
+                break;
+            case MtpConstants.PROPERTY_DURATION:
+                column = Audio.AudioColumns.DURATION;
+                type = MtpConstants.TYPE_UINT32;
+                break;
+            case MtpConstants.PROPERTY_TRACK:
+                column = Audio.AudioColumns.TRACK;
+                type = MtpConstants.TYPE_UINT16;
+                break;
+            case MtpConstants.PROPERTY_DISPLAY_NAME:
+                column = MediaColumns.DISPLAY_NAME;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_ARTIST:
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_ALBUM_NAME:
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_ALBUM_ARTIST:
+                column = Audio.AudioColumns.ALBUM_ARTIST;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_GENRE:
+                // genre requires a special query
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_COMPOSER:
+                column = Audio.AudioColumns.COMPOSER;
+                type = MtpConstants.TYPE_STR;
+                break;
+            case MtpConstants.PROPERTY_DESCRIPTION:
+                column = Images.ImageColumns.DESCRIPTION;
+                type = MtpConstants.TYPE_STR;
+                break;
+            default:
+                return new MtpPropertyList(0, MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED);
+        }
+
+        Cursor c = null;
+        try {
+            if (column != null) {
+                c = mMediaProvider.query(mObjectsUri,
+                        new String [] { Files.FileColumns._ID, column },
+                        // depth 0: single record, depth 1: immediate children
+                        (depth == 0 ? ID_WHERE : PARENT_WHERE),
+                        new String[] { Integer.toString(handle) }, null);
+                if (c == null) {
+                    return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+                }
+            } else if (depth == 1) {
+                c = mMediaProvider.query(mObjectsUri,
+                        new String [] { Files.FileColumns._ID },
+                        PARENT_WHERE, new String[] { Integer.toString(handle) }, null);
+                if (c == null) {
+                    return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+                }
+            }
+
+            int count = (c == null ? 1 : c.getCount());
+            MtpPropertyList result = new MtpPropertyList(count, MtpConstants.RESPONSE_OK);
+
+            for (int index = 0; index < count; index++) {
+                if (c != null) {
+                    c.moveToNext();
+                }
+                if (depth == 1) {
+                    handle = (int)c.getLong(0);
+                }
+
+                switch (property) {
+                    // handle special cases here
+                    case MtpConstants.PROPERTY_STORAGE_ID:
+                        result.setProperty(index, handle, property, MtpConstants.TYPE_UINT32,
+                                mStorageID);
+                        break;
+                    case MtpConstants.PROPERTY_PROTECTION_STATUS:
+                        // protection status is always 0
+                        result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16, 0);
+                        break;
+                    case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
+                        // special case - need to extract file name from full path
+                        String value = c.getString(1);
+                        if (value != null) {
+                            result.setProperty(index, handle, property, nameFromPath(value));
+                        } else {
+                            result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+                        }
+                        break;
+                    case MtpConstants.PROPERTY_NAME:
+                        // first try title
+                        String name = c.getString(1);
+                        // then try name
+                        if (name == null) {
+                            name = queryString(handle, Audio.PlaylistsColumns.NAME);
+                        }
+                        // if title and name fail, extract name from full path
+                        if (name == null) {
+                            name = queryString(handle, Files.FileColumns.DATA);
+                            if (name != null) {
+                                name = nameFromPath(name);
+                            }
+                        }
+                        if (name != null) {
+                            result.setProperty(index, handle, property, name);
+                        } else {
+                            result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+                        }
+                        break;
+                    case MtpConstants.PROPERTY_DATE_MODIFIED:
+                    case MtpConstants.PROPERTY_DATE_ADDED:
+                        // convert from seconds to DateTime
+                        result.setProperty(index, handle, property, formatDateTime(c.getInt(1)));
+                        break;
+                    case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
+                        // release date is stored internally as just the year
+                        int year = c.getInt(1);
+                        String dateTime = Integer.toString(year) + "0101T000000";
+                        result.setProperty(index, handle, property, dateTime);
+                        break;
+                    case MtpConstants.PROPERTY_PERSISTENT_UID:
+                        // PUID is concatenation of storageID and object handle
+                        long puid = mStorageID;
+                        puid <<= 32;
+                        puid += handle;
+                        result.setProperty(index, handle, property, MtpConstants.TYPE_UINT128, puid);
+                        break;
+                    case MtpConstants.PROPERTY_TRACK:
+                        result.setProperty(index, handle, property, MtpConstants.TYPE_UINT16,
+                                    c.getInt(1) % 1000);
+                        break;
+                    case MtpConstants.PROPERTY_ARTIST:
+                        result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ARTIST));
+                        break;
+                    case MtpConstants.PROPERTY_ALBUM_NAME:
+                        result.setProperty(index, handle, property, queryAudio(handle, Audio.AudioColumns.ALBUM));
+                        break;
+                    case MtpConstants.PROPERTY_GENRE:
+                        String genre = queryGenre(handle);
+                        if (genre != null) {
+                            result.setProperty(index, handle, property, genre);
+                        } else {
+                            result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
+                        }
+                        break;
+                    default:
+                        if (type == MtpConstants.TYPE_STR) {
+                            result.setProperty(index, handle, property, c.getString(1));
+                        } else {
+                            result.setProperty(index, handle, property, type, c.getLong(1));
+                        }
+                }
+            }
+
+            return result;
+        } catch (RemoteException e) {
+            return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        // impossible to get here, so no return statement
+    }
+
     private int renameFile(int handle, String newName) {
         Cursor c = null;
 
@@ -543,141 +794,6 @@
         return MtpConstants.RESPONSE_OK;
     }
 
-    private int getObjectProperty(int handle, int property,
-                            long[] outIntValue, char[] outStringValue) {
-        Log.d(TAG, "getObjectProperty: " + property);
-        String column = null;
-        boolean isString = false;
-
-        switch (property) {
-            case MtpConstants.PROPERTY_STORAGE_ID:
-                outIntValue[0] = mStorageID;
-                return MtpConstants.RESPONSE_OK;
-            case MtpConstants.PROPERTY_OBJECT_FORMAT:
-                column = Files.FileColumns.FORMAT;
-                break;
-            case MtpConstants.PROPERTY_PROTECTION_STATUS:
-                // protection status is always 0
-                outIntValue[0] = 0;
-                return MtpConstants.RESPONSE_OK;
-            case MtpConstants.PROPERTY_OBJECT_SIZE:
-                column = Files.FileColumns.SIZE;
-                break;
-            case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
-                // special case - need to extract file name from full path
-                String value = queryString(handle, Files.FileColumns.DATA);
-                if (value != null) {
-                    value = nameFromPath(value);
-                    value.getChars(0, value.length(), outStringValue, 0);
-                    outStringValue[value.length()] = 0;
-                    return MtpConstants.RESPONSE_OK;
-                } else {
-                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-                }
-            case MtpConstants.PROPERTY_NAME:
-                // first try title
-                String name = queryString(handle, MediaColumns.TITLE);
-                // then try name
-                if (name == null) {
-                    name = queryString(handle, Audio.PlaylistsColumns.NAME);
-                }
-                // if title and name fail, extract name from full path
-                if (name == null) {
-                    name = queryString(handle, Files.FileColumns.DATA);
-                    if (name != null) {
-                        name = nameFromPath(name);
-                    }
-                }
-                if (name != null) {
-                    name.getChars(0, name.length(), outStringValue, 0);
-                    outStringValue[name.length()] = 0;
-                    return MtpConstants.RESPONSE_OK;
-                } else {
-                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-                }
-            case MtpConstants.PROPERTY_DATE_MODIFIED:
-                column = Files.FileColumns.DATE_MODIFIED;
-                break;
-            case MtpConstants.PROPERTY_DATE_ADDED:
-                column = Files.FileColumns.DATE_ADDED;
-                break;
-            case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
-                column = Audio.AudioColumns.YEAR;
-                break;
-            case MtpConstants.PROPERTY_PARENT_OBJECT:
-                column = Files.FileColumns.PARENT;
-                break;
-            case MtpConstants.PROPERTY_PERSISTENT_UID:
-                // PUID is concatenation of storageID and object handle
-                long puid = mStorageID;
-                puid <<= 32;
-                puid += handle;
-                outIntValue[0] = puid;
-                return MtpConstants.RESPONSE_OK;
-            case MtpConstants.PROPERTY_DURATION:
-                column = Audio.AudioColumns.DURATION;
-                break;
-            case MtpConstants.PROPERTY_TRACK:
-                if (queryInt(handle, Audio.AudioColumns.TRACK, outIntValue)) {
-                    // track is stored in lower 3 decimal digits
-                    outIntValue[0] %= 1000;
-                    return MtpConstants.RESPONSE_OK;
-                } else {
-                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-                }
-            case MtpConstants.PROPERTY_DISPLAY_NAME:
-                column = MediaColumns.DISPLAY_NAME;
-                isString = true;
-                break;
-            case MtpConstants.PROPERTY_ARTIST:
-                column = Audio.AudioColumns.ARTIST;
-                isString = true;
-                break;
-            case MtpConstants.PROPERTY_ALBUM_NAME:
-                column = Audio.AudioColumns.ALBUM;
-                isString = true;
-                break;
-            case MtpConstants.PROPERTY_ALBUM_ARTIST:
-                column = Audio.AudioColumns.ALBUM_ARTIST;
-                isString = true;
-                break;
-            case MtpConstants.PROPERTY_GENRE:
-                String genre = queryGenre(handle);
-                if (genre != null) {
-                    genre.getChars(0, genre.length(), outStringValue, 0);
-                    outStringValue[genre.length()] = 0;
-                    return MtpConstants.RESPONSE_OK;
-                } else {
-                    return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-                }
-            case MtpConstants.PROPERTY_COMPOSER:
-                column = Audio.AudioColumns.COMPOSER;
-                isString = true;
-                break;
-            case MtpConstants.PROPERTY_DESCRIPTION:
-                column = Images.ImageColumns.DESCRIPTION;
-                isString = true;
-                break;
-            default:
-                return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
-        }
-
-        if (isString) {
-            String value = queryString(handle, column);
-            if (value != null) {
-                value.getChars(0, value.length(), outStringValue, 0);
-                outStringValue[value.length()] = 0;
-                return MtpConstants.RESPONSE_OK;
-            }
-        } else {
-            if (queryInt(handle, column, outIntValue)) {
-                return MtpConstants.RESPONSE_OK;
-            }
-        }
-        // query failed if we get here
-        return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
-    }
-
     private int setObjectProperty(int handle, int property,
                             long intValue, String stringValue) {
         Log.d(TAG, "setObjectProperty: " + property);
diff --git a/media/java/android/media/MtpPropertyList.java b/media/java/android/media/MtpPropertyList.java
new file mode 100644
index 0000000..f598981
--- /dev/null
+++ b/media/java/android/media/MtpPropertyList.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+/**
+ * Encapsulates the ObjectPropList dataset used by the GetObjectPropList command.
+ * The fields of this class are read by JNI code in android_media_MtpDatabase.cpp
+ *
+ * {@hide}
+ */
+
+public class MtpPropertyList {
+
+    // number of results returned
+    public final int        mCount;
+    // result code for GetObjectPropList
+    public int              mResult;
+    // list of object handles (first field in quadruplet)
+    public final int[]      mObjectHandles;
+    // list of object propery codes (second field in quadruplet)
+    public final int[]      mPropertyCodes;
+    // list of data type codes (third field in quadruplet)
+    public final int[]     mDataTypes;
+    // list of long int property values (fourth field in quadruplet, when value is integer type)
+    public long[]     mLongValues;
+    // list of long int property values (fourth field in quadruplet, when value is string type)
+    public String[]   mStringValues;
+
+    // constructor only called from MtpDatabase
+    public MtpPropertyList(int count, int result) {
+        mCount = count;
+        mResult = result;
+        mObjectHandles = new int[count];
+        mPropertyCodes = new int[count];
+        mDataTypes = new int[count];
+        // mLongValues and mStringValues are created lazily since both might not be necessary
+    }
+
+    public void setProperty(int index, int handle, int property, int type, long value) {
+        if (mLongValues == null) {
+            mLongValues = new long[mCount];
+        }
+        mObjectHandles[index] = handle;
+        mPropertyCodes[index] = property;
+        mDataTypes[index] = type;
+        mLongValues[index] = value;
+    }
+
+    public void setProperty(int index, int handle, int property, String value) {
+        if (mStringValues == null) {
+            mStringValues = new String[mCount];
+        }
+        mObjectHandles[index] = handle;
+        mPropertyCodes[index] = property;
+        mDataTypes[index] = MtpConstants.TYPE_STR;
+        mStringValues[index] = value;
+    }
+
+    public void setResult(int result) {
+        mResult = result;
+    }
+}
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
index 87cb82e..aa29de8 100644
--- a/media/jni/android_media_MtpDatabase.cpp
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -46,10 +46,10 @@
 static jmethodID method_getSupportedCaptureFormats;
 static jmethodID method_getSupportedObjectProperties;
 static jmethodID method_getSupportedDeviceProperties;
-static jmethodID method_getObjectProperty;
 static jmethodID method_setObjectProperty;
 static jmethodID method_getDeviceProperty;
 static jmethodID method_setDeviceProperty;
+static jmethodID method_getObjectPropertyList;
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
 static jmethodID method_deleteFile;
@@ -60,6 +60,16 @@
 
 static jfieldID field_context;
 
+// MtpPropertyList fields
+static jfieldID field_mCount;
+static jfieldID field_mResult;
+static jfieldID field_mObjectHandles;
+static jfieldID field_mPropertyCodes;
+static jfieldID field_mDataTypes;
+static jfieldID field_mLongValues;
+static jfieldID field_mStringValues;
+
+
 MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
     return (MtpDatabase *)env->GetIntField(database, field_context);
 }
@@ -122,6 +132,12 @@
 
     virtual MtpResponseCode         resetDeviceProperty(MtpDeviceProperty property);
 
+    virtual MtpResponseCode         getObjectPropertyList(MtpObjectHandle handle,
+                                            MtpObjectFormat format,
+                                            MtpObjectProperty property,
+                                            int groupCode, int depth,
+                                            MtpDataPacket& packet);
+
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
                                             MtpDataPacket& packet);
 
@@ -336,84 +352,111 @@
 MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
                                             MtpObjectProperty property,
                                             MtpDataPacket& packet) {
-    int         type;
-
-    if (!getObjectPropertyInfo(property, type))
-        return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
-
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jint result = env->CallIntMethod(mDatabase, method_getObjectProperty,
-                (jint)handle, (jint)property, mLongBuffer, mStringBuffer);
-    if (result != MTP_RESPONSE_OK) {
-        checkAndClearExceptionFromCallback(env, __FUNCTION__);
-        return result;
+    jobject list = env->CallObjectMethod(mDatabase, method_getObjectPropertyList,
+                (jint)handle, 0, (jint)property, 0, 0);
+    MtpResponseCode result = env->GetIntField(list, field_mResult);
+    int count = env->GetIntField(list, field_mCount);
+    if (result == MTP_RESPONSE_OK && count != 1)
+        result = MTP_RESPONSE_GENERAL_ERROR;
+
+    if (result == MTP_RESPONSE_OK) {
+        jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
+        jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
+        jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
+        jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
+        jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+
+        jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
+        jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
+        jint* dataTypes = env->GetIntArrayElements(dataTypesArray, 0);
+        jlong* longValues = (longValuesArray ? env->GetLongArrayElements(longValuesArray, 0) : NULL);
+
+        int type = dataTypes[0];
+        jlong longValue = (longValues ? longValues[0] : 0);
+
+        // special case date properties, which are strings to MTP
+        // but stored internally as a uint64
+        if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
+            char    date[20];
+            formatDateTime(longValue, date, sizeof(date));
+            packet.putString(date);
+            goto out;
+        }
+        // release date is stored internally as just the year
+        if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) {
+            char    date[20];
+            snprintf(date, sizeof(date), "%04lld0101T000000", longValue);
+            packet.putString(date);
+            goto out;
+        }
+
+        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_INT128:
+                packet.putInt128(longValue);
+                break;
+            case MTP_TYPE_UINT128:
+                packet.putInt128(longValue);
+                break;
+            case MTP_TYPE_STR:
+            {
+                jstring stringValue = (jstring)env->GetObjectArrayElement(stringValuesArray, 0);
+                if (stringValue) {
+                    const char* str = env->GetStringUTFChars(stringValue, NULL);
+                    packet.putString(str);
+                    env->ReleaseStringUTFChars(stringValue, str);
+                } else {
+                    packet.putEmptyString();
+                }
+                break;
+             }
+            default:
+                LOGE("unsupported type in getObjectPropertyValue\n");
+                result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
+        }
+out:
+        env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
+        env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
+        env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
+        if (longValues)
+            env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+
+        env->DeleteLocalRef(objectHandlesArray);
+        env->DeleteLocalRef(propertyCodesArray);
+        env->DeleteLocalRef(dataTypesArray);
+        if (longValuesArray)
+            env->DeleteLocalRef(longValuesArray);
+        if (stringValuesArray)
+            env->DeleteLocalRef(stringValuesArray);
     }
 
-    jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
-    jlong longValue = longValues[0];
-    env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
-
-    // special case date properties, which are strings to MTP
-    // but stored internally as a uint64
-    if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
-        char    date[20];
-        formatDateTime(longValue, date, sizeof(date));
-        packet.putString(date);
-        return MTP_RESPONSE_OK;
-    }
-    // release date is stored internally as just the year
-    if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) {
-        char    date[20];
-        snprintf(date, sizeof(date), "%04lld0101T000000", longValue);
-        packet.putString(date);
-        return MTP_RESPONSE_OK;
-    }
-
-    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_INT128:
-            packet.putInt128(longValue);
-            break;
-        case MTP_TYPE_UINT128:
-            packet.putInt128(longValue);
-            break;
-        case MTP_TYPE_STR:
-        {
-            jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
-            packet.putString(str);
-            env->ReleaseCharArrayElements(mStringBuffer, str, 0);
-            break;
-         }
-        default:
-            LOGE("unsupported type in getObjectPropertyValue\n");
-            return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
-    }
-
+    env->DeleteLocalRef(list);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return MTP_RESPONSE_OK;
+    return result;
 }
 
 MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
@@ -601,6 +644,106 @@
     return -1;
 }
 
+MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
+                                            MtpObjectFormat format,
+                                            MtpObjectProperty property,
+                                            int groupCode, int depth,
+                                            MtpDataPacket& packet) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jobject list = env->CallObjectMethod(mDatabase, method_getObjectPropertyList,
+                (jint)handle, (jint)format, (jint)property, (jint)groupCode, (jint)depth);
+    int count = env->GetIntField(list, field_mCount);
+    MtpResponseCode result = env->GetIntField(list, field_mResult);
+
+    packet.putUInt32(count);
+
+    if (count > 0) {
+        jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
+        jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
+        jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
+        jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
+        jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+
+        jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
+        jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
+        jint* dataTypes = env->GetIntArrayElements(dataTypesArray, 0);
+        jlong* longValues = (longValuesArray ? env->GetLongArrayElements(longValuesArray, 0) : NULL);
+
+        for (int i = 0; i < count; i++) {
+            packet.putUInt32(objectHandles[i]);
+            packet.putUInt16(propertyCodes[i]);
+            int type = dataTypes[i];
+            packet.putUInt16(type);
+
+            switch (type) {
+                case MTP_TYPE_INT8:
+                    packet.putInt8(longValues[i]);
+                    break;
+                case MTP_TYPE_UINT8:
+                    packet.putUInt8(longValues[i]);
+                    break;
+                case MTP_TYPE_INT16:
+                    packet.putInt16(longValues[i]);
+                    break;
+                case MTP_TYPE_UINT16:
+                    packet.putUInt16(longValues[i]);
+                    break;
+                case MTP_TYPE_INT32:
+                    packet.putInt32(longValues[i]);
+                    break;
+                case MTP_TYPE_UINT32:
+                    packet.putUInt32(longValues[i]);
+                    break;
+                case MTP_TYPE_INT64:
+                    packet.putInt64(longValues[i]);
+                    break;
+                case MTP_TYPE_UINT64:
+                    packet.putUInt64(longValues[i]);
+                    break;
+                case MTP_TYPE_INT128:
+                    packet.putInt128(longValues[i]);
+                    break;
+                case MTP_TYPE_UINT128:
+                    packet.putUInt128(longValues[i]);
+                    break;
+                case MTP_TYPE_STR: {
+                    jstring value = (jstring)env->GetObjectArrayElement(stringValuesArray, i);
+                    const char *valueStr = env->GetStringUTFChars(value, NULL);
+                    if (valueStr) {
+                        packet.putString(valueStr);
+                        env->ReleaseStringUTFChars(value, valueStr);
+                    } else {
+                        packet.putEmptyString();
+                    }
+                    env->DeleteLocalRef(value);
+                    break;
+                }
+                default:
+                    LOGE("bad or unsupported data type in MyMtpDatabase::getObjectPropertyList");
+                    break;
+            }
+        }
+
+        env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
+        env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
+        env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
+        if (longValues)
+            env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+
+        env->DeleteLocalRef(objectHandlesArray);
+        env->DeleteLocalRef(propertyCodesArray);
+        env->DeleteLocalRef(dataTypesArray);
+        if (longValuesArray)
+            env->DeleteLocalRef(longValuesArray);
+        if (stringValuesArray)
+            env->DeleteLocalRef(stringValuesArray);
+    }
+
+    env->DeleteLocalRef(list);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    return result;
+}
+
 MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
                                             MtpDataPacket& packet) {
     char    date[20];
@@ -954,11 +1097,6 @@
         LOGE("Can't find getSupportedDeviceProperties");
         return -1;
     }
-    method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I");
-    if (method_getObjectProperty == NULL) {
-        LOGE("Can't find getObjectProperty");
-        return -1;
-    }
     method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I");
     if (method_setObjectProperty == NULL) {
         LOGE("Can't find setObjectProperty");
@@ -974,6 +1112,12 @@
         LOGE("Can't find setDeviceProperty");
         return -1;
     }
+    method_getObjectPropertyList = env->GetMethodID(clazz, "getObjectPropertyList",
+            "(IIIII)Landroid/media/MtpPropertyList;");
+    if (method_getObjectPropertyList == NULL) {
+        LOGE("Can't find getObjectPropertyList");
+        return -1;
+    }
     method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
     if (method_getObjectInfo == NULL) {
         LOGE("Can't find getObjectInfo");
@@ -1016,6 +1160,48 @@
         return -1;
     }
 
+    // now set up fields for MtpPropertyList class
+    clazz = env->FindClass("android/media/MtpPropertyList");
+    if (clazz == NULL) {
+        LOGE("Can't find android/media/MtpPropertyList");
+        return -1;
+    }
+    field_mCount = env->GetFieldID(clazz, "mCount", "I");
+    if (field_mCount == NULL) {
+        LOGE("Can't find MtpPropertyList.mCount");
+        return -1;
+    }
+    field_mResult = env->GetFieldID(clazz, "mResult", "I");
+    if (field_mResult == NULL) {
+        LOGE("Can't find MtpPropertyList.mResult");
+        return -1;
+    }
+    field_mObjectHandles = env->GetFieldID(clazz, "mObjectHandles", "[I");
+    if (field_mObjectHandles == NULL) {
+        LOGE("Can't find MtpPropertyList.mObjectHandles");
+        return -1;
+    }
+    field_mPropertyCodes = env->GetFieldID(clazz, "mPropertyCodes", "[I");
+    if (field_mPropertyCodes == NULL) {
+        LOGE("Can't find MtpPropertyList.mPropertyCodes");
+        return -1;
+    }
+    field_mDataTypes = env->GetFieldID(clazz, "mDataTypes", "[I");
+    if (field_mDataTypes == NULL) {
+        LOGE("Can't find MtpPropertyList.mDataTypes");
+        return -1;
+    }
+    field_mLongValues = env->GetFieldID(clazz, "mLongValues", "[J");
+    if (field_mLongValues == NULL) {
+        LOGE("Can't find MtpPropertyList.mLongValues");
+        return -1;
+    }
+    field_mStringValues = env->GetFieldID(clazz, "mStringValues", "[Ljava/lang/String;");
+    if (field_mStringValues == NULL) {
+        LOGE("Can't find MtpPropertyList.mStringValues");
+        return -1;
+    }
+
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MtpDatabase", gMethods, NELEM(gMethods));
 }
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index c8cb016..fafd221 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -75,6 +75,12 @@
 
     virtual MtpResponseCode         resetDeviceProperty(MtpDeviceProperty property) = 0;
 
+    virtual MtpResponseCode         getObjectPropertyList(MtpObjectHandle handle,
+                                            MtpObjectFormat format,
+                                            MtpObjectProperty property,
+                                            int groupCode, int depth,
+                                            MtpDataPacket& packet) = 0;
+
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
                                             MtpDataPacket& packet) = 0;
 
diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp
index 3416807..1668ecf 100644
--- a/media/mtp/MtpDebug.cpp
+++ b/media/mtp/MtpDebug.cpp
@@ -56,6 +56,10 @@
     { "MTP_OPERATION_GET_OBJECT_PROP_DESC",         0x9802 },
     { "MTP_OPERATION_GET_OBJECT_PROP_VALUE",        0x9803 },
     { "MTP_OPERATION_SET_OBJECT_PROP_VALUE",        0x9804 },
+    { "MTP_OPERATION_GET_OBJECT_PROP_LIST",         0x9805 },
+    { "MTP_OPERATION_SET_OBJECT_PROP_LIST",         0x9806 },
+    { "MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC", 0x9807 },
+    { "MTP_OPERATION_SEND_OBJECT_PROP_LIST",        0x9808 },
     { "MTP_OPERATION_GET_OBJECT_REFERENCES",        0x9810 },
     { "MTP_OPERATION_SET_OBJECT_REFERENCES",        0x9811 },
     { "MTP_OPERATION_SKIP",                         0x9820 },
@@ -371,15 +375,21 @@
     return getCodeName(code, sOperationCodes);
 }
 
-const char* MtpDebug::getFormatCodeName(MtpOperationCode code) {
+const char* MtpDebug::getFormatCodeName(MtpObjectFormat code) {
+    if (code == 0)
+        return "NONE";
     return getCodeName(code, sFormatCodes);
 }
 
 const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) {
+    if (code == 0)
+        return "NONE";
     return getCodeName(code, sObjectPropCodes);
 }
 
 const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) {
+    if (code == 0)
+        return "NONE";
     return getCodeName(code, sDevicePropCodes);
 }
 
diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp
index f7c12d6..86889c3 100644
--- a/media/mtp/MtpProperty.cpp
+++ b/media/mtp/MtpProperty.cpp
@@ -53,7 +53,7 @@
         mDefaultArrayValues(NULL),
         mCurrentArrayLength(0),
         mCurrentArrayValues(NULL),
-        mGroupCode(0),
+        mGroupCode(-1), // disable multiple properties in GetObjectPropList for now
         mFormFlag(kFormNone),
         mEnumLength(0),
         mEnumValues(NULL)
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 5ba6be9..6cf70ec 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -72,6 +72,10 @@
     MTP_OPERATION_GET_OBJECT_PROP_DESC,
     MTP_OPERATION_GET_OBJECT_PROP_VALUE,
     MTP_OPERATION_SET_OBJECT_PROP_VALUE,
+    MTP_OPERATION_GET_OBJECT_PROP_LIST,
+//    MTP_OPERATION_SET_OBJECT_PROP_LIST,
+//    MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
+//    MTP_OPERATION_SEND_OBJECT_PROP_LIST,
     MTP_OPERATION_GET_OBJECT_REFERENCES,
     MTP_OPERATION_SET_OBJECT_REFERENCES,
 //    MTP_OPERATION_SKIP,
@@ -276,6 +280,9 @@
         case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
             response = doResetDevicePropValue();
             break;
+        case MTP_OPERATION_GET_OBJECT_PROP_LIST:
+            response = doGetObjectPropList();
+            break;
         case MTP_OPERATION_GET_OBJECT_INFO:
             response = doGetObjectInfo();
             break;
@@ -523,6 +530,20 @@
     return mDatabase->resetDeviceProperty(property);
 }
 
+MtpResponseCode MtpServer::doGetObjectPropList() {
+
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    MtpObjectFormat format = mRequest.getParameter(2);
+    MtpDeviceProperty property = mRequest.getParameter(3);
+    int groupCode = mRequest.getParameter(4);
+    int depth = mRequest.getParameter(4);
+   LOGD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
+            handle, MtpDebug::getFormatCodeName(format),
+            MtpDebug::getObjectPropCodeName(property), groupCode, depth);
+
+    return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
+}
+
 MtpResponseCode MtpServer::doGetObjectInfo() {
     MtpObjectHandle handle = mRequest.getParameter(1);
     return mDatabase->getObjectInfo(handle, mData);
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index 68a6564..e65ddb0 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -93,6 +93,7 @@
     MtpResponseCode     doGetDevicePropValue();
     MtpResponseCode     doSetDevicePropValue();
     MtpResponseCode     doResetDevicePropValue();
+    MtpResponseCode     doGetObjectPropList();
     MtpResponseCode     doGetObjectInfo();
     MtpResponseCode     doGetObject();
     MtpResponseCode     doSendObjectInfo();
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index b7afa66..8bc2e22 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -37,7 +37,7 @@
 #define MTP_CONTAINER_PARAMETER_OFFSET          12
 #define MTP_CONTAINER_HEADER_SIZE               12
 
-// MTP Types
+// MTP Data Types
 #define MTP_TYPE_UNDEFINED      0x0000          // Undefined
 #define MTP_TYPE_INT8           0x0001          // Signed 8-bit integer
 #define MTP_TYPE_UINT8          0x0002          // Unsigned 8-bit integer
@@ -383,6 +383,10 @@
 #define MTP_OPERATION_GET_OBJECT_PROP_DESC                  0x9802
 #define MTP_OPERATION_GET_OBJECT_PROP_VALUE                 0x9803
 #define MTP_OPERATION_SET_OBJECT_PROP_VALUE                 0x9804
+#define MTP_OPERATION_GET_OBJECT_PROP_LIST                  0x9805
+#define MTP_OPERATION_SET_OBJECT_PROP_LIST                  0x9806
+#define MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC          0x9807
+#define MTP_OPERATION_SEND_OBJECT_PROP_LIST                 0x9808
 #define MTP_OPERATION_GET_OBJECT_REFERENCES                 0x9810
 #define MTP_OPERATION_SET_OBJECT_REFERENCES                 0x9811
 #define MTP_OPERATION_SKIP                                  0x9820