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