From 7d7fb63071ca370f77ed69055ac30341b8388d15 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Wed, 1 Dec 2010 18:46:23 -0500 Subject: [PATCH] MTP: Add support for multiple properties in GetObjectPropList command Change-Id: Ib469a6c8141937aaa3e811345056195117abb4b2 Signed-off-by: Mike Lockwood --- media/java/android/media/MtpDatabase.java | 373 +++------------ .../java/android/media/MtpPropertyGroup.java | 442 ++++++++++++++++++ media/java/android/media/MtpPropertyList.java | 28 +- media/jni/android_media_MtpDatabase.cpp | 31 +- media/mtp/MtpDatabase.h | 3 +- media/mtp/MtpProperty.cpp | 2 +- media/mtp/MtpServer.cpp | 5 +- 7 files changed, 545 insertions(+), 339 deletions(-) create mode 100644 media/java/android/media/MtpPropertyGroup.java diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java index 03879892799db..139a6ea3468cd 100644 --- a/media/java/android/media/MtpDatabase.java +++ b/media/java/android/media/MtpDatabase.java @@ -33,6 +33,7 @@ import android.provider.MediaStore.MediaColumns; import android.util.Log; import java.io.File; +import java.util.HashMap; /** * {@hide} @@ -48,6 +49,14 @@ public class MtpDatabase { private final String mMediaStoragePath; private final String mExternalStoragePath; + // cached property groups for single properties + private final HashMap mPropertyGroupsByProperty + = new HashMap(); + + // cached property groups for all properties for a given format + private final HashMap mPropertyGroupsByFormat + = new HashMap(); + // true if the database has been modified in the current MTP session private boolean mDatabaseModified; @@ -386,6 +395,43 @@ public class MtpDatabase { MtpConstants.PROPERTY_DESCRIPTION, }; + static final int[] ALL_PROPERTIES = { + // NOTE must match FILE_PROPERTIES above + MtpConstants.PROPERTY_STORAGE_ID, + MtpConstants.PROPERTY_OBJECT_FORMAT, + MtpConstants.PROPERTY_PROTECTION_STATUS, + MtpConstants.PROPERTY_OBJECT_SIZE, + MtpConstants.PROPERTY_OBJECT_FILE_NAME, + MtpConstants.PROPERTY_DATE_MODIFIED, + MtpConstants.PROPERTY_PARENT_OBJECT, + MtpConstants.PROPERTY_PERSISTENT_UID, + MtpConstants.PROPERTY_NAME, + MtpConstants.PROPERTY_DISPLAY_NAME, + MtpConstants.PROPERTY_DATE_ADDED, + + // image specific properties + MtpConstants.PROPERTY_DESCRIPTION, + + // audio specific properties + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_ALBUM_ARTIST, + MtpConstants.PROPERTY_TRACK, + MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_GENRE, + MtpConstants.PROPERTY_COMPOSER, + + // video specific properties + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_DESCRIPTION, + + // image specific properties + MtpConstants.PROPERTY_DESCRIPTION, + }; + private int[] getSupportedObjectProperties(int format) { switch (format) { case MtpConstants.FORMAT_MP3: @@ -403,6 +449,8 @@ public class MtpDatabase { case MtpConstants.FORMAT_PNG: case MtpConstants.FORMAT_BMP: return IMAGE_PROPERTIES; + case 0: + return ALL_PROPERTIES; default: return FILE_PROPERTIES; } @@ -415,324 +463,32 @@ public class MtpDatabase { }; } - private String queryString(int id, String column) { - Cursor c = null; - try { - // for now we are only reading properties from the "objects" table - c = mMediaProvider.query(mObjectsUri, - 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 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 { - Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id); - c = mMediaProvider.query(uri, - new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME }, - null, null, null); - if (c != null && c.moveToNext()) { - return c.getString(1); - } else { - return ""; - } - } catch (Exception e) { - Log.e(TAG, "queryGenre exception", e); - return null; - } finally { - if (c != null) { - c.close(); - } - } - } - - private Long queryLong(int id, String column) { - Cursor c = null; - try { - // for now we are only reading properties from the "objects" table - c = mMediaProvider.query(mObjectsUri, - new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null); - if (c != null && c.moveToNext()) { - return new Long(c.getLong(1)); - } - } catch (Exception e) { - } finally { - if (c != null) { - c.close(); - } - } - return null; - } - - private String nameFromPath(String path) { - // extract name from full path - int start = 0; - int lastSlash = path.lastIndexOf('/'); - if (lastSlash >= 0) { - start = lastSlash + 1; - } - int end = path.length(); - if (end - start > 255) { - end = start + 255; - } - return path.substring(start, end); - } - - private MtpPropertyList getObjectPropertyList(int handle, int format, int property, + private MtpPropertyList getObjectPropertyList(long handle, int format, long 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); - } + MtpPropertyGroup propertyGroup; + if (property == 0xFFFFFFFFL) { + propertyGroup = mPropertyGroupsByFormat.get(format); + if (propertyGroup == null) { + int[] propertyList = getSupportedObjectProperties(format); + propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); + mPropertyGroupsByFormat.put(new Integer(format), propertyGroup); } - - 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, format_date_time(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(); + } else { + propertyGroup = mPropertyGroupsByProperty.get(property); + if (propertyGroup == null) { + int[] propertyList = new int[] { (int)property }; + propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mVolumeName, propertyList); + mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup); } } - // impossible to get here, so no return statement + + return propertyGroup.getPropertyList((int)handle, format, depth, mStorageID); } private int renameFile(int handle, String newName) { @@ -1028,5 +784,4 @@ public class MtpDatabase { private native final void native_setup(); private native final void native_finalize(); - private native String format_date_time(long seconds); } diff --git a/media/java/android/media/MtpPropertyGroup.java b/media/java/android/media/MtpPropertyGroup.java new file mode 100644 index 0000000000000..bb733e2210f9c --- /dev/null +++ b/media/java/android/media/MtpPropertyGroup.java @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.content.IContentProvider; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio; +import android.provider.MediaStore.Files; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.MediaColumns; +import android.util.Log; + +import java.util.ArrayList; + +class MtpPropertyGroup { + + private static final String TAG = "MtpPropertyGroup"; + + private class Property { + // MTP property code + int code; + // MTP data type + int type; + // column index for our query + int column; + + Property(int code, int type, int column) { + this.code = code; + this.type = type; + this.column = column; + } + } + + private final MtpDatabase mDatabase; + private final IContentProvider mProvider; + private final String mVolumeName; + private final Uri mUri; + + // list of all properties in this group + private final Property[] mProperties; + + // list of columns for database query + private String[] mColumns; + + private static final String ID_WHERE = Files.FileColumns._ID + "=?"; + private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + + Files.FileColumns.FORMAT + "=?"; + private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; + private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + + Files.FileColumns.FORMAT + "=?"; + // constructs a property group for a list of properties + public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String volume, + int[] properties) { + mDatabase = database; + mProvider = provider; + mVolumeName = volume; + mUri = Files.getMtpObjectsUri(volume); + + int count = properties.length; + ArrayList columns = new ArrayList(count); + columns.add(Files.FileColumns._ID); + + mProperties = new Property[count]; + for (int i = 0; i < count; i++) { + mProperties[i] = createProperty(properties[i], columns); + } + count = columns.size(); + mColumns = new String[count]; + for (int i = 0; i < count; i++) { + mColumns[i] = columns.get(i); + } + } + + private Property createProperty(int code, ArrayList columns) { + String column = null; + int type; + + switch (code) { + case MtpConstants.PROPERTY_STORAGE_ID: + // no query needed until we support multiple storage units + 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: + type = MtpConstants.TYPE_UNDEFINED; + Log.e(TAG, "unsupported property " + code); + break; + } + + if (column != null) { + columns.add(column); + return new Property(code, type, columns.size() - 1); + } else { + return new Property(code, type, -1); + } + } + + private String queryString(int id, String column) { + Cursor c = null; + try { + // for now we are only reading properties from the "objects" table + c = mProvider.query(mUri, + 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 queryAudio(int id, String column) { + Cursor c = null; + try { + c = mProvider.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 { + Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id); + c = mProvider.query(uri, + new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME }, + null, null, null); + if (c != null && c.moveToNext()) { + return c.getString(1); + } else { + return ""; + } + } catch (Exception e) { + Log.e(TAG, "queryGenre exception", e); + return null; + } finally { + if (c != null) { + c.close(); + } + } + } + + private Long queryLong(int id, String column) { + Cursor c = null; + try { + // for now we are only reading properties from the "objects" table + c = mProvider.query(mUri, + new String [] { Files.FileColumns._ID, column }, + ID_WHERE, new String[] { Integer.toString(id) }, null); + if (c != null && c.moveToNext()) { + return new Long(c.getLong(1)); + } + } catch (Exception e) { + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + + private static String nameFromPath(String path) { + // extract name from full path + int start = 0; + int lastSlash = path.lastIndexOf('/'); + if (lastSlash >= 0) { + start = lastSlash + 1; + } + int end = path.length(); + if (end - start > 255) { + end = start + 255; + } + return path.substring(start, end); + } + + MtpPropertyList getPropertyList(int handle, int format, int depth, int storageID) { + Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth); + if (depth > 1) { + // we only support depth 0 and 1 + // depth 0: single object, depth 1: immediate children + return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); + } + + String where; + String[] whereArgs; + if (format == 0) { + whereArgs = new String[] { Integer.toString(handle) }; + if (depth == 1) { + where = PARENT_WHERE; + } else { + where = ID_WHERE; + } + } else { + whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) }; + if (depth == 1) { + where = PARENT_FORMAT_WHERE; + } else { + where = ID_FORMAT_WHERE; + } + } + + Cursor c = null; + try { + // don't query if not necessary + if (depth > 0 || mColumns.length > 1) { + c = mProvider.query(mUri, mColumns, where, whereArgs, 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 * mProperties.length, + MtpConstants.RESPONSE_OK); + + // iterate over all objects in the query + for (int objectIndex = 0; objectIndex < count; objectIndex++) { + if (c != null) { + c.moveToNext(); + } + if (depth == 1) { + handle = (int)c.getLong(0); + } + + // iterate over all properties in the query for the given object + for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) { + Property property = mProperties[propertyIndex]; + int propertyCode = property.code; + int column = property.column; + + // handle some special cases + switch (propertyCode) { + case MtpConstants.PROPERTY_STORAGE_ID: + result.append(handle, propertyCode, MtpConstants.TYPE_UINT32, + storageID); + break; + case MtpConstants.PROPERTY_PROTECTION_STATUS: + // protection status is always 0 + result.append(handle, propertyCode, 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(column); + if (value != null) { + result.append(handle, propertyCode, nameFromPath(value)); + } else { + result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); + } + break; + case MtpConstants.PROPERTY_NAME: + // first try title + String name = c.getString(column); + // 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.append(handle, propertyCode, 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.append(handle, propertyCode, format_date_time(c.getInt(column))); + break; + case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: + // release date is stored internally as just the year + int year = c.getInt(column); + String dateTime = Integer.toString(year) + "0101T000000"; + result.append(handle, propertyCode, dateTime); + break; + case MtpConstants.PROPERTY_PERSISTENT_UID: + // PUID is concatenation of storageID and object handle + long puid = storageID; + puid <<= 32; + puid += handle; + result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid); + break; + case MtpConstants.PROPERTY_TRACK: + result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, + c.getInt(column) % 1000); + break; + case MtpConstants.PROPERTY_ARTIST: + result.append(handle, propertyCode, + queryAudio(handle, Audio.AudioColumns.ARTIST)); + break; + case MtpConstants.PROPERTY_ALBUM_NAME: + result.append(handle, propertyCode, + queryAudio(handle, Audio.AudioColumns.ALBUM)); + break; + case MtpConstants.PROPERTY_GENRE: + String genre = queryGenre(handle); + if (genre != null) { + result.append(handle, propertyCode, genre); + } else { + result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); + } + break; + default: + if (property.type == MtpConstants.TYPE_STR) { + result.append(handle, propertyCode, c.getString(column)); + } else if (property.type == MtpConstants.TYPE_UNDEFINED) { + result.append(handle, propertyCode, property.type, 0); + } else { + result.append(handle, propertyCode, property.type, + c.getLong(column)); + } + break; + } + } + } + + 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 native String format_date_time(long seconds); +} diff --git a/media/java/android/media/MtpPropertyList.java b/media/java/android/media/MtpPropertyList.java index f598981b45b91..d3f0b34e41dd0 100644 --- a/media/java/android/media/MtpPropertyList.java +++ b/media/java/android/media/MtpPropertyList.java @@ -19,14 +19,14 @@ 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 { +class MtpPropertyList { // number of results returned - public final int mCount; + private int mCount; + // maximum number of results + private final int mMaxCount; // result code for GetObjectPropList public int mResult; // list of object handles (first field in quadruplet) @@ -41,18 +41,19 @@ public class MtpPropertyList { public String[] mStringValues; // constructor only called from MtpDatabase - public MtpPropertyList(int count, int result) { - mCount = count; + public MtpPropertyList(int maxCount, int result) { + mMaxCount = maxCount; mResult = result; - mObjectHandles = new int[count]; - mPropertyCodes = new int[count]; - mDataTypes = new int[count]; + mObjectHandles = new int[maxCount]; + mPropertyCodes = new int[maxCount]; + mDataTypes = new int[maxCount]; // 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) { + public void append(int handle, int property, int type, long value) { + int index = mCount++; if (mLongValues == null) { - mLongValues = new long[mCount]; + mLongValues = new long[mMaxCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; @@ -60,9 +61,10 @@ public class MtpPropertyList { mLongValues[index] = value; } - public void setProperty(int index, int handle, int property, String value) { + public void append(int handle, int property, String value) { + int index = mCount++; if (mStringValues == null) { - mStringValues = new String[mCount]; + mStringValues = new String[mMaxCount]; } mObjectHandles[index] = handle; mPropertyCodes[index] = property; diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp index f04a2ae941f1e..1909e6a562889 100644 --- a/media/jni/android_media_MtpDatabase.cpp +++ b/media/jni/android_media_MtpDatabase.cpp @@ -134,8 +134,7 @@ public: virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty property); virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle handle, - MtpObjectFormat format, - MtpObjectProperty property, + uint32_t format, uint32_t property, int groupCode, int depth, MtpDataPacket& packet); @@ -355,7 +354,7 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, MtpDataPacket& packet) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject list = env->CallObjectMethod(mDatabase, method_getObjectPropertyList, - (jint)handle, 0, (jint)property, 0, 0); + (jlong)handle, 0, (jlong)property, 0, 0); MtpResponseCode result = env->GetIntField(list, field_mResult); int count = env->GetIntField(list, field_mCount); if (result == MTP_RESPONSE_OK && count != 1) @@ -646,18 +645,19 @@ MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty property) { } MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, - MtpObjectFormat format, - MtpObjectProperty property, + uint32_t format, uint32_t 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); + (jlong)handle, (jint)format, (jlong)property, (jint)groupCode, (jint)depth); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + if (!list) + return MTP_RESPONSE_GENERAL_ERROR; 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); @@ -1042,7 +1042,7 @@ android_media_MtpDatabase_finalize(JNIEnv *env, jobject thiz) } static jstring -android_media_MtpDatabase_format_date_time(JNIEnv *env, jobject thiz, jlong seconds) +android_media_MtpPropertyGroup_format_date_time(JNIEnv *env, jobject thiz, jlong seconds) { #ifdef HAVE_ANDROID_OS char date[20]; @@ -1055,11 +1055,14 @@ android_media_MtpDatabase_format_date_time(JNIEnv *env, jobject thiz, jlong seco // ---------------------------------------------------------------------------- -static JNINativeMethod gMethods[] = { +static JNINativeMethod gMtpDatabaseMethods[] = { {"native_setup", "()V", (void *)android_media_MtpDatabase_setup}, {"native_finalize", "()V", (void *)android_media_MtpDatabase_finalize}, +}; + +static JNINativeMethod gMtpPropertyGroupMethods[] = { {"format_date_time", "(J)Ljava/lang/String;", - (void *)android_media_MtpDatabase_format_date_time}, + (void *)android_media_MtpPropertyGroup_format_date_time}, }; static const char* const kClassPathName = "android/media/MtpDatabase"; @@ -1131,7 +1134,7 @@ int register_android_media_MtpDatabase(JNIEnv *env) return -1; } method_getObjectPropertyList = env->GetMethodID(clazz, "getObjectPropertyList", - "(IIIII)Landroid/media/MtpPropertyList;"); + "(JIJII)Landroid/media/MtpPropertyList;"); if (method_getObjectPropertyList == NULL) { LOGE("Can't find getObjectPropertyList"); return -1; @@ -1220,6 +1223,10 @@ int register_android_media_MtpDatabase(JNIEnv *env) return -1; } + if (AndroidRuntime::registerNativeMethods(env, + "android/media/MtpDatabase", gMtpDatabaseMethods, NELEM(gMtpDatabaseMethods))) + return -1; + return AndroidRuntime::registerNativeMethods(env, - "android/media/MtpDatabase", gMethods, NELEM(gMethods)); + "android/media/MtpPropertyGroup", gMtpPropertyGroupMethods, NELEM(gMtpPropertyGroupMethods)); } diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index 900b517d3ba53..992980570a92d 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -77,8 +77,7 @@ public: virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty property) = 0; virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle handle, - MtpObjectFormat format, - MtpObjectProperty property, + uint32_t format, uint32_t property, int groupCode, int depth, MtpDataPacket& packet) = 0; diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp index 42945f5a1da0e..4356a6f48e44c 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(-1), // disable multiple properties in GetObjectPropList for now + mGroupCode(0), mFormFlag(kFormNone), mEnumLength(0), mEnumValues(NULL) diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index c3755f31c1a33..de6cbac9026d9 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -536,8 +536,9 @@ MtpResponseCode MtpServer::doResetDevicePropValue() { MtpResponseCode MtpServer::doGetObjectPropList() { MtpObjectHandle handle = mRequest.getParameter(1); - MtpObjectFormat format = mRequest.getParameter(2); - MtpDeviceProperty property = mRequest.getParameter(3); + // use uint32_t so we can support 0xFFFFFFFF + uint32_t format = mRequest.getParameter(2); + uint32_t property = mRequest.getParameter(3); int groupCode = mRequest.getParameter(4); int depth = mRequest.getParameter(5); LOGD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",