Merge "MTP: Add support for multiple properties in GetObjectPropList command"

This commit is contained in:
Mike Lockwood
2010-12-02 06:53:12 -08:00
committed by Android (Google) Code Review
7 changed files with 545 additions and 339 deletions

View File

@@ -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<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
= new HashMap<Integer, MtpPropertyGroup>();
// cached property groups for all properties for a given format
private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat
= new HashMap<Integer, MtpPropertyGroup>();
// 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);
}

View File

@@ -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<String> columns = new ArrayList<String>(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<String> 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);
}

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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",