diff options
author | Mike Lockwood <lockwood@google.com> | 2010-10-14 18:03:25 -0400 |
---|---|---|
committer | Mike Lockwood <lockwood@android.com> | 2010-11-15 11:46:51 -0500 |
commit | e2ad6ec1718ef0c0e8230f8f62e7cfefcf598b6a (patch) | |
tree | 0f7de42a1ed233d704558830643e73acd954b940 /media/java | |
parent | 44d47ad56502a1ccb308a9ec2cd05120a53fac3d (diff) | |
download | frameworks_base-e2ad6ec1718ef0c0e8230f8f62e7cfefcf598b6a.zip frameworks_base-e2ad6ec1718ef0c0e8230f8f62e7cfefcf598b6a.tar.gz frameworks_base-e2ad6ec1718ef0c0e8230f8f62e7cfefcf598b6a.tar.bz2 |
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>
Diffstat (limited to 'media/java')
-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 |
3 files changed, 355 insertions, 139 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; + } +} |