diff options
-rw-r--r-- | media/java/android/media/MtpConstants.java | 24 | ||||
-rw-r--r-- | media/java/android/media/MtpDatabase.java | 394 | ||||
-rw-r--r-- | media/java/android/media/MtpPropertyList.java | 76 | ||||
-rw-r--r-- | media/jni/android_media_MtpDatabase.cpp | 344 | ||||
-rw-r--r-- | media/mtp/MtpDatabase.h | 6 | ||||
-rw-r--r-- | media/mtp/MtpDebug.cpp | 12 | ||||
-rw-r--r-- | media/mtp/MtpProperty.cpp | 2 | ||||
-rw-r--r-- | media/mtp/MtpServer.cpp | 21 | ||||
-rw-r--r-- | media/mtp/MtpServer.h | 1 | ||||
-rw-r--r-- | media/mtp/mtp.h | 6 |
10 files changed, 665 insertions, 221 deletions
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 @@ package android.media; */ 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.Files; 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 @@ public class MtpDatabase { } } + 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 @@ public class MtpDatabase { } } - 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 @@ public class MtpDatabase { 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,197 +504,294 @@ public class MtpDatabase { return path.substring(start, end); } - private int renameFile(int handle, String newName) { - Cursor c = null; - - // first compute current path - String path = null; - String[] whereArgs = new String[] { Integer.toString(handle) }; - try { - c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); - if (c != null && c.moveToNext()) { - path = externalToMediaPath(c.getString(1)); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectFilePath", e); - return MtpConstants.RESPONSE_GENERAL_ERROR; - } finally { - if (c != null) { - c.close(); - } - } - if (path == null) { - return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; - } - - // now rename the file. make sure this succeeds before updating database - File oldFile = new File(path); - int lastSlash = path.lastIndexOf('/'); - if (lastSlash <= 1) { - return MtpConstants.RESPONSE_GENERAL_ERROR; - } - String newPath = path.substring(0, lastSlash + 1) + newName; - File newFile = new File(newPath); - boolean success = oldFile.renameTo(newFile); - Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); - if (!success) { - return MtpConstants.RESPONSE_GENERAL_ERROR; - } + 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; + } - // finally update database - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, newPath); - int updated = 0; - try { - // note - we are relying on a special case in MediaProvider.update() to update - // the paths for all children in the case where this is a directory. - updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in mMediaProvider.update", e); + 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 (updated == 0) { - Log.e(TAG, "Unable to update path for " + path + " to " + newPath); - // this shouldn't happen, but if it does we need to rename the file to its original name - newFile.renameTo(oldFile); - return MtpConstants.RESPONSE_GENERAL_ERROR; + if (depth > 1) { + return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); } - 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; + int type = MtpConstants.TYPE_UNDEFINED; - switch (property) { + switch (property) { case MtpConstants.PROPERTY_STORAGE_ID: - outIntValue[0] = mStorageID; - return MtpConstants.RESPONSE_OK; - case MtpConstants.PROPERTY_OBJECT_FORMAT: + // 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 - outIntValue[0] = 0; - return MtpConstants.RESPONSE_OK; + 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: - // 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; - } + column = Files.FileColumns.DATA; + type = MtpConstants.TYPE_STR; + break; 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; - } + 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 - long puid = mStorageID; - puid <<= 32; - puid += handle; - outIntValue[0] = puid; - return MtpConstants.RESPONSE_OK; + type = MtpConstants.TYPE_UINT128; + break; case MtpConstants.PROPERTY_DURATION: column = Audio.AudioColumns.DURATION; + type = MtpConstants.TYPE_UINT32; 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; - } + column = Audio.AudioColumns.TRACK; + type = MtpConstants.TYPE_UINT16; + break; case MtpConstants.PROPERTY_DISPLAY_NAME: column = MediaColumns.DISPLAY_NAME; - isString = true; + type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ARTIST: - column = Audio.AudioColumns.ARTIST; - isString = true; + type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_NAME: - column = Audio.AudioColumns.ALBUM; - isString = true; + type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ALBUM_ARTIST: column = Audio.AudioColumns.ALBUM_ARTIST; - isString = true; + type = MtpConstants.TYPE_STR; 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; - } + // genre requires a special query + type = MtpConstants.TYPE_STR; + break; case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; - isString = true; + type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DESCRIPTION: column = Images.ImageColumns.DESCRIPTION; - isString = true; + type = MtpConstants.TYPE_STR; break; default: - return MtpConstants.RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + return new MtpPropertyList(0, 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; + 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); + } } - } else { - if (queryInt(handle, column, outIntValue)) { - return MtpConstants.RESPONSE_OK; + + 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(); } } - // query failed if we get here - return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + // impossible to get here, so no return statement + } + + private int renameFile(int handle, String newName) { + Cursor c = null; + + // first compute current path + String path = null; + String[] whereArgs = new String[] { Integer.toString(handle) }; + try { + c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, whereArgs, null); + if (c != null && c.moveToNext()) { + path = externalToMediaPath(c.getString(1)); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in getObjectFilePath", e); + return MtpConstants.RESPONSE_GENERAL_ERROR; + } finally { + if (c != null) { + c.close(); + } + } + if (path == null) { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } + + // now rename the file. make sure this succeeds before updating database + File oldFile = new File(path); + int lastSlash = path.lastIndexOf('/'); + if (lastSlash <= 1) { + return MtpConstants.RESPONSE_GENERAL_ERROR; + } + String newPath = path.substring(0, lastSlash + 1) + newName; + File newFile = new File(newPath); + boolean success = oldFile.renameTo(newFile); + Log.d(TAG, "renaming "+ path + " to " + newPath + (success ? " succeeded" : " failed")); + if (!success) { + return MtpConstants.RESPONSE_GENERAL_ERROR; + } + + // finally update database + ContentValues values = new ContentValues(); + values.put(Files.FileColumns.DATA, newPath); + int updated = 0; + try { + // note - we are relying on a special case in MediaProvider.update() to update + // the paths for all children in the case where this is a directory. + updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in mMediaProvider.update", e); + } + if (updated == 0) { + Log.e(TAG, "Unable to update path for " + path + " to " + newPath); + // this shouldn't happen, but if it does we need to rename the file to its original name + newFile.renameTo(oldFile); + return MtpConstants.RESPONSE_GENERAL_ERROR; + } + + return MtpConstants.RESPONSE_OK; } private int setObjectProperty(int handle, int 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_getSupportedPlaybackFormats; 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 jmethodID method_sessionEnded; 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 @@ public: 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 @@ MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { 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; - } - - 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; - } + 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: - { - 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; + 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); } + env->DeleteLocalRef(list); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return MTP_RESPONSE_OK; + return result; } MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, @@ -601,6 +644,106 @@ MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty property) { 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 @@ int register_android_media_MtpDatabase(JNIEnv *env) 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 @@ int register_android_media_MtpDatabase(JNIEnv *env) 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 @@ int register_android_media_MtpDatabase(JNIEnv *env) 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 @@ public: 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 @@ static const CodeEntry sOperationCodes[] = { { "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 @@ const char* MtpDebug::getOperationCodeName(MtpOperationCode code) { 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 @@ MtpProperty::MtpProperty(MtpPropertyCode propCode, 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 @@ static const MtpOperationCode kSupportedOperationCodes[] = { 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 @@ bool MtpServer::handleRequest() { 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 @@ MtpResponseCode MtpServer::doResetDevicePropValue() { 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 @@ private: 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 |