Merge "Add MtpStorageManager for monitoring filesystem events"
This commit is contained in:
committed by
Android (Google) Code Review
commit
205523d0a2
@@ -19,7 +19,6 @@ package android.os.storage;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcel;
|
||||
@@ -78,13 +77,11 @@ import java.io.File;
|
||||
public final class StorageVolume implements Parcelable {
|
||||
|
||||
private final String mId;
|
||||
private final int mStorageId;
|
||||
private final File mPath;
|
||||
private final String mDescription;
|
||||
private final boolean mPrimary;
|
||||
private final boolean mRemovable;
|
||||
private final boolean mEmulated;
|
||||
private final long mMtpReserveSize;
|
||||
private final boolean mAllowMassStorage;
|
||||
private final long mMaxFileSize;
|
||||
private final UserHandle mOwner;
|
||||
@@ -121,17 +118,15 @@ public final class StorageVolume implements Parcelable {
|
||||
public static final int STORAGE_ID_PRIMARY = 0x00010001;
|
||||
|
||||
/** {@hide} */
|
||||
public StorageVolume(String id, int storageId, File path, String description, boolean primary,
|
||||
boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
|
||||
public StorageVolume(String id, File path, String description, boolean primary,
|
||||
boolean removable, boolean emulated, boolean allowMassStorage,
|
||||
long maxFileSize, UserHandle owner, String fsUuid, String state) {
|
||||
mId = Preconditions.checkNotNull(id);
|
||||
mStorageId = storageId;
|
||||
mPath = Preconditions.checkNotNull(path);
|
||||
mDescription = Preconditions.checkNotNull(description);
|
||||
mPrimary = primary;
|
||||
mRemovable = removable;
|
||||
mEmulated = emulated;
|
||||
mMtpReserveSize = mtpReserveSize;
|
||||
mAllowMassStorage = allowMassStorage;
|
||||
mMaxFileSize = maxFileSize;
|
||||
mOwner = Preconditions.checkNotNull(owner);
|
||||
@@ -141,13 +136,11 @@ public final class StorageVolume implements Parcelable {
|
||||
|
||||
private StorageVolume(Parcel in) {
|
||||
mId = in.readString();
|
||||
mStorageId = in.readInt();
|
||||
mPath = new File(in.readString());
|
||||
mDescription = in.readString();
|
||||
mPrimary = in.readInt() != 0;
|
||||
mRemovable = in.readInt() != 0;
|
||||
mEmulated = in.readInt() != 0;
|
||||
mMtpReserveSize = in.readLong();
|
||||
mAllowMassStorage = in.readInt() != 0;
|
||||
mMaxFileSize = in.readLong();
|
||||
mOwner = in.readParcelable(null);
|
||||
@@ -210,34 +203,6 @@ public final class StorageVolume implements Parcelable {
|
||||
return mEmulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MTP storage ID for the volume.
|
||||
* this is also used for the storage_id column in the media provider.
|
||||
*
|
||||
* @return MTP storage ID
|
||||
* @hide
|
||||
*/
|
||||
public int getStorageId() {
|
||||
return mStorageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of megabytes of space to leave unallocated by MTP.
|
||||
* MTP will subtract this value from the free space it reports back
|
||||
* to the host via GetStorageInfo, and will not allow new files to
|
||||
* be added via MTP if there is less than this amount left free in the storage.
|
||||
* If MTP has dedicated storage this value should be zero, but if MTP is
|
||||
* sharing storage with the rest of the system, set this to a positive value
|
||||
* to ensure that MTP activity does not result in the storage being
|
||||
* too close to full.
|
||||
*
|
||||
* @return MTP reserve space
|
||||
* @hide
|
||||
*/
|
||||
public int getMtpReserveSpace() {
|
||||
return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this volume can be shared via USB mass storage.
|
||||
*
|
||||
@@ -385,13 +350,11 @@ public final class StorageVolume implements Parcelable {
|
||||
pw.println("StorageVolume:");
|
||||
pw.increaseIndent();
|
||||
pw.printPair("mId", mId);
|
||||
pw.printPair("mStorageId", mStorageId);
|
||||
pw.printPair("mPath", mPath);
|
||||
pw.printPair("mDescription", mDescription);
|
||||
pw.printPair("mPrimary", mPrimary);
|
||||
pw.printPair("mRemovable", mRemovable);
|
||||
pw.printPair("mEmulated", mEmulated);
|
||||
pw.printPair("mMtpReserveSize", mMtpReserveSize);
|
||||
pw.printPair("mAllowMassStorage", mAllowMassStorage);
|
||||
pw.printPair("mMaxFileSize", mMaxFileSize);
|
||||
pw.printPair("mOwner", mOwner);
|
||||
@@ -420,13 +383,11 @@ public final class StorageVolume implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeString(mId);
|
||||
parcel.writeInt(mStorageId);
|
||||
parcel.writeString(mPath.toString());
|
||||
parcel.writeString(mDescription);
|
||||
parcel.writeInt(mPrimary ? 1 : 0);
|
||||
parcel.writeInt(mRemovable ? 1 : 0);
|
||||
parcel.writeInt(mEmulated ? 1 : 0);
|
||||
parcel.writeLong(mMtpReserveSize);
|
||||
parcel.writeInt(mAllowMassStorage ? 1 : 0);
|
||||
parcel.writeLong(mMaxFileSize);
|
||||
parcel.writeParcelable(mOwner, flags);
|
||||
|
||||
@@ -343,9 +343,7 @@ public class VolumeInfo implements Parcelable {
|
||||
|
||||
String description = null;
|
||||
String derivedFsUuid = fsUuid;
|
||||
long mtpReserveSize = 0;
|
||||
long maxFileSize = 0;
|
||||
int mtpStorageId = StorageVolume.STORAGE_ID_INVALID;
|
||||
|
||||
if (type == TYPE_EMULATED) {
|
||||
emulated = true;
|
||||
@@ -356,12 +354,6 @@ public class VolumeInfo implements Parcelable {
|
||||
derivedFsUuid = privateVol.fsUuid;
|
||||
}
|
||||
|
||||
if (isPrimary()) {
|
||||
mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
|
||||
}
|
||||
|
||||
mtpReserveSize = storage.getStorageLowBytes(userPath);
|
||||
|
||||
if (ID_EMULATED_INTERNAL.equals(id)) {
|
||||
removable = false;
|
||||
} else {
|
||||
@@ -374,14 +366,6 @@ public class VolumeInfo implements Parcelable {
|
||||
|
||||
description = storage.getBestVolumeDescription(this);
|
||||
|
||||
if (isPrimary()) {
|
||||
mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
|
||||
} else {
|
||||
// Since MediaProvider currently persists this value, we need a
|
||||
// value that is stable over time.
|
||||
mtpStorageId = buildStableMtpStorageId(fsUuid);
|
||||
}
|
||||
|
||||
if ("vfat".equals(fsType)) {
|
||||
maxFileSize = 4294967295L;
|
||||
}
|
||||
@@ -394,8 +378,8 @@ public class VolumeInfo implements Parcelable {
|
||||
description = context.getString(android.R.string.unknownName);
|
||||
}
|
||||
|
||||
return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
|
||||
emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
|
||||
return new StorageVolume(id, userPath, description, isPrimary(), removable,
|
||||
emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
|
||||
derivedFsUuid, envState);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,15 +63,6 @@ public final class MediaStore {
|
||||
|
||||
private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
|
||||
|
||||
/**
|
||||
* Broadcast Action: A broadcast to indicate the end of an MTP session with the host.
|
||||
* This broadcast is only sent if MTP activity has modified the media database during the
|
||||
* most recent MTP session.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
|
||||
|
||||
/**
|
||||
* The method name used by the media scanner and mtp to tell the media provider to
|
||||
* rescan and reclassify that have become unhidden because of renaming folders or
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,22 +23,21 @@ import android.os.RemoteException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* MtpPropertyGroup represents a list of MTP properties.
|
||||
* {@hide}
|
||||
*/
|
||||
class MtpPropertyGroup {
|
||||
|
||||
private static final String TAG = "MtpPropertyGroup";
|
||||
private static final String TAG = MtpPropertyGroup.class.getSimpleName();
|
||||
|
||||
private class Property {
|
||||
// MTP property code
|
||||
int code;
|
||||
// MTP data type
|
||||
int type;
|
||||
// column index for our query
|
||||
int column;
|
||||
int code;
|
||||
int type;
|
||||
int column;
|
||||
|
||||
Property(int code, int type, int column) {
|
||||
this.code = code;
|
||||
@@ -47,32 +46,26 @@ class MtpPropertyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private final MtpDatabase mDatabase;
|
||||
private final ContentProviderClient mProvider;
|
||||
private final String mVolumeName;
|
||||
private final Uri mUri;
|
||||
|
||||
// list of all properties in this group
|
||||
private final Property[] mProperties;
|
||||
private final Property[] mProperties;
|
||||
|
||||
// list of columns for database query
|
||||
private String[] mColumns;
|
||||
private String[] mColumns;
|
||||
|
||||
private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
|
||||
|
||||
private static final String ID_WHERE = Files.FileColumns._ID + "=?";
|
||||
private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
|
||||
private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE;
|
||||
private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
|
||||
private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
|
||||
// constructs a property group for a list of properties
|
||||
public MtpPropertyGroup(MtpDatabase database, ContentProviderClient provider, String volumeName,
|
||||
int[] properties) {
|
||||
mDatabase = database;
|
||||
public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
|
||||
mProvider = provider;
|
||||
mVolumeName = volumeName;
|
||||
mUri = Files.getMtpObjectsUri(volumeName);
|
||||
|
||||
int count = properties.length;
|
||||
ArrayList<String> columns = new ArrayList<String>(count);
|
||||
ArrayList<String> columns = new ArrayList<>(count);
|
||||
columns.add(Files.FileColumns._ID);
|
||||
|
||||
mProperties = new Property[count];
|
||||
@@ -90,37 +83,29 @@ class MtpPropertyGroup {
|
||||
String column = null;
|
||||
int type;
|
||||
|
||||
switch (code) {
|
||||
switch (code) {
|
||||
case MtpConstants.PROPERTY_STORAGE_ID:
|
||||
column = Files.FileColumns.STORAGE_ID;
|
||||
type = MtpConstants.TYPE_UINT32;
|
||||
break;
|
||||
case MtpConstants.PROPERTY_OBJECT_FORMAT:
|
||||
column = Files.FileColumns.FORMAT;
|
||||
case MtpConstants.PROPERTY_OBJECT_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:
|
||||
@@ -128,12 +113,9 @@ class MtpPropertyGroup {
|
||||
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
|
||||
column = Files.FileColumns.STORAGE_ID;
|
||||
type = MtpConstants.TYPE_UINT128;
|
||||
break;
|
||||
case MtpConstants.PROPERTY_DURATION:
|
||||
@@ -145,7 +127,6 @@ class MtpPropertyGroup {
|
||||
type = MtpConstants.TYPE_UINT16;
|
||||
break;
|
||||
case MtpConstants.PROPERTY_DISPLAY_NAME:
|
||||
column = MediaColumns.DISPLAY_NAME;
|
||||
type = MtpConstants.TYPE_STR;
|
||||
break;
|
||||
case MtpConstants.PROPERTY_ARTIST:
|
||||
@@ -195,40 +176,19 @@ class MtpPropertyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
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, 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) {
|
||||
private String queryAudio(String path, 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, null);
|
||||
new String [] { column },
|
||||
PATH_WHERE, new String[] {path}, null, null);
|
||||
if (c != null && c.moveToNext()) {
|
||||
return c.getString(1);
|
||||
return c.getString(0);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
return "";
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
@@ -236,21 +196,19 @@ class MtpPropertyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private String queryGenre(int id) {
|
||||
private String queryGenre(String path) {
|
||||
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, null);
|
||||
c = mProvider.query(Audio.Genres.getContentUri(mVolumeName),
|
||||
new String [] { Audio.GenresColumns.NAME },
|
||||
PATH_WHERE, new String[] {path}, null, null);
|
||||
if (c != null && c.moveToNext()) {
|
||||
return c.getString(1);
|
||||
return c.getString(0);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "queryGenre exception", e);
|
||||
return null;
|
||||
return "";
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
@@ -258,211 +216,127 @@ class MtpPropertyGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private Long queryLong(int id, String column) {
|
||||
/**
|
||||
* Gets the values of the properties represented by this property group for the given
|
||||
* object and adds them to the given property list.
|
||||
* @return Response_OK if the operation succeeded.
|
||||
*/
|
||||
public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) {
|
||||
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, 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) {
|
||||
//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) {
|
||||
if (handle == 0xFFFFFFFF) {
|
||||
// select all objects
|
||||
where = null;
|
||||
whereArgs = null;
|
||||
} else {
|
||||
whereArgs = new String[] { Integer.toString(handle) };
|
||||
if (depth == 1) {
|
||||
where = PARENT_WHERE;
|
||||
} else {
|
||||
where = ID_WHERE;
|
||||
int id = object.getId();
|
||||
String path = object.getPath().toString();
|
||||
for (Property property : mProperties) {
|
||||
if (property.column != -1 && c == null) {
|
||||
try {
|
||||
// Look up the entry in MediaProvider only if one of those properties is needed.
|
||||
c = mProvider.query(mUri, mColumns,
|
||||
PATH_WHERE, new String[] {path}, null, null);
|
||||
if (c != null && !c.moveToNext()) {
|
||||
c.close();
|
||||
c = null;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Mediaprovider lookup failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (handle == 0xFFFFFFFF) {
|
||||
// select all objects with given format
|
||||
where = FORMAT_WHERE;
|
||||
whereArgs = new String[] { Integer.toString(format) };
|
||||
} 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 || handle == 0xFFFFFFFF || mColumns.length > 1) {
|
||||
c = mProvider.query(mUri, mColumns, where, whereArgs, null, 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();
|
||||
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_PROTECTION_STATUS:
|
||||
// protection status is always 0
|
||||
result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
|
||||
switch (property.code) {
|
||||
case MtpConstants.PROPERTY_PROTECTION_STATUS:
|
||||
// protection status is always 0
|
||||
list.append(id, property.code, property.type, 0);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_NAME:
|
||||
case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
|
||||
case MtpConstants.PROPERTY_DISPLAY_NAME:
|
||||
list.append(id, property.code, object.getName());
|
||||
break;
|
||||
case MtpConstants.PROPERTY_DATE_MODIFIED:
|
||||
case MtpConstants.PROPERTY_DATE_ADDED:
|
||||
// convert from seconds to DateTime
|
||||
list.append(id, property.code,
|
||||
format_date_time(object.getModifiedTime()));
|
||||
break;
|
||||
case MtpConstants.PROPERTY_STORAGE_ID:
|
||||
list.append(id, property.code, property.type, object.getStorageId());
|
||||
break;
|
||||
case MtpConstants.PROPERTY_OBJECT_FORMAT:
|
||||
list.append(id, property.code, property.type, object.getFormat());
|
||||
break;
|
||||
case MtpConstants.PROPERTY_OBJECT_SIZE:
|
||||
list.append(id, property.code, property.type, object.getSize());
|
||||
break;
|
||||
case MtpConstants.PROPERTY_PARENT_OBJECT:
|
||||
list.append(id, property.code, property.type,
|
||||
object.getParent().isRoot() ? 0 : object.getParent().getId());
|
||||
break;
|
||||
case MtpConstants.PROPERTY_PERSISTENT_UID:
|
||||
// The persistent uid must be unique and never reused among all objects,
|
||||
// and remain the same between sessions.
|
||||
long puid = (object.getPath().toString().hashCode() << 32)
|
||||
+ object.getModifiedTime();
|
||||
list.append(id, property.code, property.type, puid);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
|
||||
// release date is stored internally as just the year
|
||||
int year = 0;
|
||||
if (c != null)
|
||||
year = c.getInt(property.column);
|
||||
String dateTime = Integer.toString(year) + "0101T000000";
|
||||
list.append(id, property.code, dateTime);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_TRACK:
|
||||
int track = 0;
|
||||
if (c != null)
|
||||
track = c.getInt(property.column);
|
||||
list.append(id, property.code, MtpConstants.TYPE_UINT16,
|
||||
track % 1000);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_ARTIST:
|
||||
list.append(id, property.code,
|
||||
queryAudio(path, Audio.AudioColumns.ARTIST));
|
||||
break;
|
||||
case MtpConstants.PROPERTY_ALBUM_NAME:
|
||||
list.append(id, property.code,
|
||||
queryAudio(path, Audio.AudioColumns.ALBUM));
|
||||
break;
|
||||
case MtpConstants.PROPERTY_GENRE:
|
||||
String genre = queryGenre(path);
|
||||
if (genre != null) {
|
||||
list.append(id, property.code, genre);
|
||||
}
|
||||
break;
|
||||
case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
|
||||
case MtpConstants.PROPERTY_AUDIO_BITRATE:
|
||||
case MtpConstants.PROPERTY_SAMPLE_RATE:
|
||||
// we don't have these in our database, so return 0
|
||||
list.append(id, property.code, MtpConstants.TYPE_UINT32, 0);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_BITRATE_TYPE:
|
||||
case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
|
||||
// we don't have these in our database, so return 0
|
||||
list.append(id, property.code, MtpConstants.TYPE_UINT16, 0);
|
||||
break;
|
||||
default:
|
||||
switch(property.type) {
|
||||
case MtpConstants.TYPE_UNDEFINED:
|
||||
list.append(id, property.code, property.type, 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 = c.getLong(column);
|
||||
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;
|
||||
case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
|
||||
case MtpConstants.PROPERTY_AUDIO_BITRATE:
|
||||
case MtpConstants.PROPERTY_SAMPLE_RATE:
|
||||
// we don't have these in our database, so return 0
|
||||
result.append(handle, propertyCode, MtpConstants.TYPE_UINT32, 0);
|
||||
break;
|
||||
case MtpConstants.PROPERTY_BITRATE_TYPE:
|
||||
case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
|
||||
// we don't have these in our database, so return 0
|
||||
result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0);
|
||||
case MtpConstants.TYPE_STR:
|
||||
String value = "";
|
||||
if (c != null)
|
||||
value = c.getString(property.column);
|
||||
list.append(id, property.code, value);
|
||||
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;
|
||||
long longValue = 0L;
|
||||
if (c != null)
|
||||
longValue = c.getLong(property.column);
|
||||
list.append(id, property.code, property.type, longValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (c != null)
|
||||
c.close();
|
||||
return MtpConstants.RESPONSE_OK;
|
||||
}
|
||||
|
||||
private native String format_date_time(long seconds);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package android.mtp;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates the ObjectPropList dataset used by the GetObjectPropList command.
|
||||
* The fields of this class are read by JNI code in android_media_MtpDatabase.cpp
|
||||
@@ -23,56 +26,70 @@ package android.mtp;
|
||||
|
||||
class MtpPropertyList {
|
||||
|
||||
// number of results returned
|
||||
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)
|
||||
public final int[] mObjectHandles;
|
||||
// list of object propery codes (second field in quadruplet)
|
||||
public final int[] mPropertyCodes;
|
||||
private List<Integer> mObjectHandles;
|
||||
// list of object property codes (second field in quadruplet)
|
||||
private List<Integer> mPropertyCodes;
|
||||
// list of data type codes (third field in quadruplet)
|
||||
public final int[] mDataTypes;
|
||||
private List<Integer> mDataTypes;
|
||||
// list of long int property values (fourth field in quadruplet, when value is integer type)
|
||||
public long[] mLongValues;
|
||||
private List<Long> mLongValues;
|
||||
// list of long int property values (fourth field in quadruplet, when value is string type)
|
||||
public String[] mStringValues;
|
||||
private List<String> mStringValues;
|
||||
|
||||
// constructor only called from MtpDatabase
|
||||
public MtpPropertyList(int maxCount, int result) {
|
||||
mMaxCount = maxCount;
|
||||
mResult = result;
|
||||
mObjectHandles = new int[maxCount];
|
||||
mPropertyCodes = new int[maxCount];
|
||||
mDataTypes = new int[maxCount];
|
||||
// mLongValues and mStringValues are created lazily since both might not be necessary
|
||||
// Return value of this operation
|
||||
private int mCode;
|
||||
|
||||
public MtpPropertyList(int code) {
|
||||
mCode = code;
|
||||
mObjectHandles = new ArrayList<>();
|
||||
mPropertyCodes = new ArrayList<>();
|
||||
mDataTypes = new ArrayList<>();
|
||||
mLongValues = new ArrayList<>();
|
||||
mStringValues = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void append(int handle, int property, int type, long value) {
|
||||
int index = mCount++;
|
||||
if (mLongValues == null) {
|
||||
mLongValues = new long[mMaxCount];
|
||||
}
|
||||
mObjectHandles[index] = handle;
|
||||
mPropertyCodes[index] = property;
|
||||
mDataTypes[index] = type;
|
||||
mLongValues[index] = value;
|
||||
mObjectHandles.add(handle);
|
||||
mPropertyCodes.add(property);
|
||||
mDataTypes.add(type);
|
||||
mLongValues.add(value);
|
||||
mStringValues.add(null);
|
||||
}
|
||||
|
||||
public void append(int handle, int property, String value) {
|
||||
int index = mCount++;
|
||||
if (mStringValues == null) {
|
||||
mStringValues = new String[mMaxCount];
|
||||
}
|
||||
mObjectHandles[index] = handle;
|
||||
mPropertyCodes[index] = property;
|
||||
mDataTypes[index] = MtpConstants.TYPE_STR;
|
||||
mStringValues[index] = value;
|
||||
mObjectHandles.add(handle);
|
||||
mPropertyCodes.add(property);
|
||||
mDataTypes.add(MtpConstants.TYPE_STR);
|
||||
mStringValues.add(value);
|
||||
mLongValues.add(0L);
|
||||
}
|
||||
|
||||
public void setResult(int result) {
|
||||
mResult = result;
|
||||
public int getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return mObjectHandles.size();
|
||||
}
|
||||
|
||||
public int[] getObjectHandles() {
|
||||
return mObjectHandles.stream().mapToInt(Integer::intValue).toArray();
|
||||
}
|
||||
|
||||
public int[] getPropertyCodes() {
|
||||
return mPropertyCodes.stream().mapToInt(Integer::intValue).toArray();
|
||||
}
|
||||
|
||||
public int[] getDataTypes() {
|
||||
return mDataTypes.stream().mapToInt(Integer::intValue).toArray();
|
||||
}
|
||||
|
||||
public long[] getLongValues() {
|
||||
return mLongValues.stream().mapToLong(Long::longValue).toArray();
|
||||
}
|
||||
|
||||
public String[] getStringValues() {
|
||||
return mStringValues.toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,15 +31,13 @@ public class MtpStorage {
|
||||
private final int mStorageId;
|
||||
private final String mPath;
|
||||
private final String mDescription;
|
||||
private final long mReserveSpace;
|
||||
private final boolean mRemovable;
|
||||
private final long mMaxFileSize;
|
||||
|
||||
public MtpStorage(StorageVolume volume, Context context) {
|
||||
mStorageId = volume.getStorageId();
|
||||
public MtpStorage(StorageVolume volume, int storageId) {
|
||||
mStorageId = storageId;
|
||||
mPath = volume.getPath();
|
||||
mDescription = volume.getDescription(context);
|
||||
mReserveSpace = volume.getMtpReserveSpace() * 1024L * 1024L;
|
||||
mDescription = volume.getDescription(null);
|
||||
mRemovable = volume.isRemovable();
|
||||
mMaxFileSize = volume.getMaxFileSize();
|
||||
}
|
||||
@@ -71,16 +69,6 @@ public class MtpStorage {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space to reserve on the storage file system.
|
||||
* This can be set to a non-zero value to prevent MTP from filling up the entire storage.
|
||||
*
|
||||
* @return reserved space in bytes.
|
||||
*/
|
||||
public final long getReserveSpace() {
|
||||
return mReserveSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the storage is removable.
|
||||
*
|
||||
|
||||
1210
media/java/android/mtp/MtpStorageManager.java
Normal file
1210
media/java/android/mtp/MtpStorageManager.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,6 @@ static jfieldID field_MtpServer_nativeContext;
|
||||
static jfieldID field_MtpStorage_storageId;
|
||||
static jfieldID field_MtpStorage_path;
|
||||
static jfieldID field_MtpStorage_description;
|
||||
static jfieldID field_MtpStorage_reserveSpace;
|
||||
static jfieldID field_MtpStorage_removable;
|
||||
static jfieldID field_MtpStorage_maxFileSize;
|
||||
|
||||
@@ -50,7 +49,7 @@ static Mutex sMutex;
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// in android_mtp_MtpDatabase.cpp
|
||||
extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
|
||||
extern IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);
|
||||
|
||||
static inline MtpServer* getMtpServer(JNIEnv *env, jobject thiz) {
|
||||
return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext);
|
||||
@@ -162,7 +161,6 @@ android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
|
||||
jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
|
||||
jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
|
||||
jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
|
||||
jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace);
|
||||
jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable);
|
||||
jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize);
|
||||
|
||||
@@ -171,7 +169,7 @@ android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
|
||||
const char *descriptionStr = env->GetStringUTFChars(description, NULL);
|
||||
if (descriptionStr != NULL) {
|
||||
MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr,
|
||||
reserveSpace, removable, maxFileSize);
|
||||
removable, maxFileSize);
|
||||
server->addStorage(storage);
|
||||
env->ReleaseStringUTFChars(path, pathStr);
|
||||
env->ReleaseStringUTFChars(description, descriptionStr);
|
||||
@@ -241,11 +239,6 @@ int register_android_mtp_MtpServer(JNIEnv *env)
|
||||
ALOGE("Can't find MtpStorage.mDescription");
|
||||
return -1;
|
||||
}
|
||||
field_MtpStorage_reserveSpace = env->GetFieldID(clazz, "mReserveSpace", "J");
|
||||
if (field_MtpStorage_reserveSpace == NULL) {
|
||||
ALOGE("Can't find MtpStorage.mReserveSpace");
|
||||
return -1;
|
||||
}
|
||||
field_MtpStorage_removable = env->GetFieldID(clazz, "mRemovable", "Z");
|
||||
if (field_MtpStorage_removable == NULL) {
|
||||
ALOGE("Can't find MtpStorage.mRemovable");
|
||||
|
||||
12
media/tests/MtpTests/Android.mk
Normal file
12
media/tests/MtpTests/Android.mk
Normal file
@@ -0,0 +1,12 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
|
||||
|
||||
LOCAL_PACKAGE_NAME := MtpTests
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
31
media/tests/MtpTests/AndroidManifest.xml
Normal file
31
media/tests/MtpTests/AndroidManifest.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.mtp" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.mtp"
|
||||
android:label="MtpTests"/>
|
||||
</manifest>
|
||||
15
media/tests/MtpTests/AndroidTest.xml
Normal file
15
media/tests/MtpTests/AndroidTest.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<configuration description="Runs sample instrumentation test.">
|
||||
<target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
|
||||
<option name="test-file-name" value="MtpTests.apk"/>
|
||||
</target_preparer>
|
||||
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
|
||||
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
|
||||
<option name="test-suite-tag" value="apct"/>
|
||||
<option name="test-tag" value="MtpTests"/>
|
||||
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
|
||||
<option name="package" value="android.mtp"/>
|
||||
<option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
|
||||
</test>
|
||||
</configuration>
|
||||
1657
media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
Normal file
1657
media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2718,15 +2718,14 @@ class StorageManagerService extends IStorageManager.Stub
|
||||
final boolean primary = true;
|
||||
final boolean removable = primaryPhysical;
|
||||
final boolean emulated = !primaryPhysical;
|
||||
final long mtpReserveSize = 0L;
|
||||
final boolean allowMassStorage = false;
|
||||
final long maxFileSize = 0L;
|
||||
final UserHandle owner = new UserHandle(userId);
|
||||
final String uuid = null;
|
||||
final String state = Environment.MEDIA_REMOVED;
|
||||
|
||||
res.add(0, new StorageVolume(id, StorageVolume.STORAGE_ID_INVALID, path,
|
||||
description, primary, removable, emulated, mtpReserveSize,
|
||||
res.add(0, new StorageVolume(id, path,
|
||||
description, primary, removable, emulated,
|
||||
allowMassStorage, maxFileSize, owner, uuid, state));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user