summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/storage/StorageVolume.java43
-rw-r--r--core/java/android/os/storage/VolumeInfo.java20
-rw-r--r--core/java/android/provider/MediaStore.java9
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java1339
-rw-r--r--media/java/android/mtp/MtpPropertyGroup.java404
-rw-r--r--media/java/android/mtp/MtpPropertyList.java95
-rw-r--r--media/java/android/mtp/MtpStorage.java18
-rw-r--r--media/java/android/mtp/MtpStorageManager.java1210
-rw-r--r--media/jni/android_mtp_MtpDatabase.cpp654
-rw-r--r--media/jni/android_mtp_MtpServer.cpp11
-rw-r--r--media/tests/MtpTests/Android.mk12
-rw-r--r--media/tests/MtpTests/AndroidManifest.xml31
-rw-r--r--media/tests/MtpTests/AndroidTest.xml15
-rw-r--r--media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java1657
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java5
15 files changed, 3967 insertions, 1556 deletions
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 1fc0b820cf06..070b8c1b0008 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -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);
@@ -211,34 +204,6 @@ public final class StorageVolume implements Parcelable {
}
/**
- * 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.
*
* @return whether mass storage is allowed
@@ -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);
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 76f79f13d9a7..d3877cac17b0 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -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);
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 32d68cd9f869..d9808a3d1412 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -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
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index ba29d2daaa0e..a647dcc2d4b9 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -30,6 +30,7 @@ import android.net.Uri;
import android.os.BatteryManager;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.storage.StorageVolume;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
@@ -40,21 +41,31 @@ import android.view.WindowManager;
import dalvik.system.CloseGuard;
+import com.google.android.collect.Sets;
+
import java.io.File;
-import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
/**
+ * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses
+ * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File
+ * operations are also reflected in MediaProvider if possible.
+ * operations
* {@hide}
*/
public class MtpDatabase implements AutoCloseable {
- private static final String TAG = "MtpDatabase";
+ private static final String TAG = MtpDatabase.class.getSimpleName();
- private final Context mUserContext;
private final Context mContext;
- private final String mPackageName;
private final ContentProviderClient mMediaProvider;
private final String mVolumeName;
private final Uri mObjectsUri;
@@ -63,86 +74,158 @@ public class MtpDatabase implements AutoCloseable {
private final AtomicBoolean mClosed = new AtomicBoolean();
private final CloseGuard mCloseGuard = CloseGuard.get();
- // path to primary storage
- private final String mMediaStoragePath;
- // if not null, restrict all queries to these subdirectories
- private final String[] mSubDirectories;
- // where clause for restricting queries to files in mSubDirectories
- private String mSubDirectoriesWhere;
- // where arguments for restricting queries to files in mSubDirectories
- private String[] mSubDirectoriesWhereArgs;
-
- private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>();
+ private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
// cached property groups for single properties
- private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty
- = new HashMap<Integer, MtpPropertyGroup>();
+ private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
// 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;
+ private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
// SharedPreferences for writable MTP device properties
private SharedPreferences mDeviceProperties;
- private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1;
- private static final String[] ID_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
+ // Cached device properties
+ private int mBatteryLevel;
+ private int mBatteryScale;
+ private int mDeviceType;
+
+ private MtpServer mServer;
+ private MtpStorageManager mManager;
+
+ private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
+ private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID};
+ private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA};
+ private static final String NO_MEDIA = ".nomedia";
+
+ static {
+ System.loadLibrary("media_jni");
+ }
+
+ private static final int[] PLAYBACK_FORMATS = {
+ // allow transferring arbitrary files
+ MtpConstants.FORMAT_UNDEFINED,
+
+ MtpConstants.FORMAT_ASSOCIATION,
+ MtpConstants.FORMAT_TEXT,
+ MtpConstants.FORMAT_HTML,
+ MtpConstants.FORMAT_WAV,
+ MtpConstants.FORMAT_MP3,
+ MtpConstants.FORMAT_MPEG,
+ MtpConstants.FORMAT_EXIF_JPEG,
+ MtpConstants.FORMAT_TIFF_EP,
+ MtpConstants.FORMAT_BMP,
+ MtpConstants.FORMAT_GIF,
+ MtpConstants.FORMAT_JFIF,
+ MtpConstants.FORMAT_PNG,
+ MtpConstants.FORMAT_TIFF,
+ MtpConstants.FORMAT_WMA,
+ MtpConstants.FORMAT_OGG,
+ MtpConstants.FORMAT_AAC,
+ MtpConstants.FORMAT_MP4_CONTAINER,
+ MtpConstants.FORMAT_MP2,
+ MtpConstants.FORMAT_3GP_CONTAINER,
+ MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
+ MtpConstants.FORMAT_WPL_PLAYLIST,
+ MtpConstants.FORMAT_M3U_PLAYLIST,
+ MtpConstants.FORMAT_PLS_PLAYLIST,
+ MtpConstants.FORMAT_XML_DOCUMENT,
+ MtpConstants.FORMAT_FLAC,
+ MtpConstants.FORMAT_DNG,
+ MtpConstants.FORMAT_HEIF,
};
- private static final String[] PATH_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.DATA, // 1
+
+ private static final int[] FILE_PROPERTIES = {
+ 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_PERSISTENT_UID,
+ MtpConstants.PROPERTY_PARENT_OBJECT,
+ MtpConstants.PROPERTY_NAME,
+ MtpConstants.PROPERTY_DISPLAY_NAME,
+ MtpConstants.PROPERTY_DATE_ADDED,
};
- private static final String[] FORMAT_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.FORMAT, // 1
+
+ private static final int[] AUDIO_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,
+ MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
+ MtpConstants.PROPERTY_BITRATE_TYPE,
+ MtpConstants.PROPERTY_AUDIO_BITRATE,
+ MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
+ MtpConstants.PROPERTY_SAMPLE_RATE,
};
- private static final String[] PATH_FORMAT_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.DATA, // 1
- Files.FileColumns.FORMAT, // 2
+
+ private static final int[] VIDEO_PROPERTIES = {
+ MtpConstants.PROPERTY_ARTIST,
+ MtpConstants.PROPERTY_ALBUM_NAME,
+ MtpConstants.PROPERTY_DURATION,
+ MtpConstants.PROPERTY_DESCRIPTION,
};
- private static final String[] OBJECT_INFO_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.STORAGE_ID, // 1
- Files.FileColumns.FORMAT, // 2
- Files.FileColumns.PARENT, // 3
- Files.FileColumns.DATA, // 4
- Files.FileColumns.DATE_ADDED, // 5
- Files.FileColumns.DATE_MODIFIED, // 6
+
+ private static final int[] IMAGE_PROPERTIES = {
+ MtpConstants.PROPERTY_DESCRIPTION,
};
- private static final String ID_WHERE = Files.FileColumns._ID + "=?";
- private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
- private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?";
- private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?";
- private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
- private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND "
- + Files.FileColumns.FORMAT + "=?";
- private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND "
- + Files.FileColumns.PARENT + "=?";
- private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND "
- + Files.FileColumns.PARENT + "=?";
- private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
- + Files.FileColumns.PARENT + "=?";
+ private static final int[] DEVICE_PROPERTIES = {
+ MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
+ MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
+ MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
+ MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
+ MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
+ };
- private MtpServer mServer;
+ private int[] getSupportedObjectProperties(int format) {
+ switch (format) {
+ case MtpConstants.FORMAT_MP3:
+ case MtpConstants.FORMAT_WAV:
+ case MtpConstants.FORMAT_WMA:
+ case MtpConstants.FORMAT_OGG:
+ case MtpConstants.FORMAT_AAC:
+ return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+ Arrays.stream(AUDIO_PROPERTIES)).toArray();
+ case MtpConstants.FORMAT_MPEG:
+ case MtpConstants.FORMAT_3GP_CONTAINER:
+ case MtpConstants.FORMAT_WMV:
+ return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+ Arrays.stream(VIDEO_PROPERTIES)).toArray();
+ case MtpConstants.FORMAT_EXIF_JPEG:
+ case MtpConstants.FORMAT_GIF:
+ case MtpConstants.FORMAT_PNG:
+ case MtpConstants.FORMAT_BMP:
+ case MtpConstants.FORMAT_DNG:
+ case MtpConstants.FORMAT_HEIF:
+ return IntStream.concat(Arrays.stream(FILE_PROPERTIES),
+ Arrays.stream(IMAGE_PROPERTIES)).toArray();
+ default:
+ return FILE_PROPERTIES;
+ }
+ }
- // read from native code
- private int mBatteryLevel;
- private int mBatteryScale;
+ private int[] getSupportedDeviceProperties() {
+ return DEVICE_PROPERTIES;
+ }
- private int mDeviceType;
+ private int[] getSupportedPlaybackFormats() {
+ return PLAYBACK_FORMATS;
+ }
- static {
- System.loadLibrary("media_jni");
+ private int[] getSupportedCaptureFormats() {
+ // no capture formats yet
+ return null;
}
private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
- @Override
+ @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
@@ -160,61 +243,42 @@ public class MtpDatabase implements AutoCloseable {
}
};
- public MtpDatabase(Context context, Context userContext, String volumeName, String storagePath,
+ public MtpDatabase(Context context, Context userContext, String volumeName,
String[] subDirectories) {
native_setup();
-
mContext = context;
- mUserContext = userContext;
- mPackageName = context.getPackageName();
mMediaProvider = userContext.getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY);
mVolumeName = volumeName;
- mMediaStoragePath = storagePath;
mObjectsUri = Files.getMtpObjectsUri(volumeName);
mMediaScanner = new MediaScanner(context, mVolumeName);
-
- mSubDirectories = subDirectories;
- if (subDirectories != null) {
- // Compute "where" string for restricting queries to subdirectories
- StringBuilder builder = new StringBuilder();
- builder.append("(");
- int count = subDirectories.length;
- for (int i = 0; i < count; i++) {
- builder.append(Files.FileColumns.DATA + "=? OR "
- + Files.FileColumns.DATA + " LIKE ?");
- if (i != count - 1) {
- builder.append(" OR ");
- }
+ mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
+ @Override
+ public void sendObjectAdded(int id) {
+ if (MtpDatabase.this.mServer != null)
+ MtpDatabase.this.mServer.sendObjectAdded(id);
}
- builder.append(")");
- mSubDirectoriesWhere = builder.toString();
-
- // Compute "where" arguments for restricting queries to subdirectories
- mSubDirectoriesWhereArgs = new String[count * 2];
- for (int i = 0, j = 0; i < count; i++) {
- String path = subDirectories[i];
- mSubDirectoriesWhereArgs[j++] = path;
- mSubDirectoriesWhereArgs[j++] = path + "/%";
+
+ @Override
+ public void sendObjectRemoved(int id) {
+ if (MtpDatabase.this.mServer != null)
+ MtpDatabase.this.mServer.sendObjectRemoved(id);
}
- }
+ }, subDirectories == null ? null : Sets.newHashSet(subDirectories));
initDeviceProperties(context);
mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);
-
mCloseGuard.open("close");
}
public void setServer(MtpServer server) {
mServer = server;
-
// always unregister before registering
try {
mContext.unregisterReceiver(mBatteryReceiver);
} catch (IllegalArgumentException e) {
// wasn't previously registered, ignore
}
-
// register for battery notifications when we are connected
if (server != null) {
mContext.registerReceiver(mBatteryReceiver,
@@ -224,6 +288,7 @@ public class MtpDatabase implements AutoCloseable {
@Override
public void close() {
+ mManager.close();
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
mMediaScanner.close();
@@ -238,24 +303,32 @@ public class MtpDatabase implements AutoCloseable {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
-
close();
} finally {
super.finalize();
}
}
- public void addStorage(MtpStorage storage) {
- mStorageMap.put(storage.getPath(), storage);
+ public void addStorage(StorageVolume storage) {
+ MtpStorage mtpStorage = mManager.addMtpStorage(storage);
+ mStorageMap.put(storage.getPath(), mtpStorage);
+ mServer.addStorage(mtpStorage);
}
- public void removeStorage(MtpStorage storage) {
+ public void removeStorage(StorageVolume storage) {
+ MtpStorage mtpStorage = mStorageMap.get(storage.getPath());
+ if (mtpStorage == null) {
+ return;
+ }
+ mServer.removeStorage(mtpStorage);
+ mManager.removeMtpStorage(mtpStorage);
mStorageMap.remove(storage.getPath());
}
private void initDeviceProperties(Context context) {
final String devicePropertiesName = "device-properties";
- mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE);
+ mDeviceProperties = context.getSharedPreferences(devicePropertiesName,
+ Context.MODE_PRIVATE);
File databaseFile = context.getDatabasePath(devicePropertiesName);
if (databaseFile.exists()) {
@@ -266,7 +339,7 @@ public class MtpDatabase implements AutoCloseable {
try {
db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null);
if (db != null) {
- c = db.query("properties", new String[] { "_id", "code", "value" },
+ c = db.query("properties", new String[]{"_id", "code", "value"},
null, null, null, null, null);
if (c != null) {
SharedPreferences.Editor e = mDeviceProperties.edit();
@@ -288,608 +361,371 @@ public class MtpDatabase implements AutoCloseable {
}
}
- // check to see if the path is contained in one of our storage subdirectories
- // returns true if we have no special subdirectories
- private boolean inStorageSubDirectory(String path) {
- if (mSubDirectories == null) return true;
- if (path == null) return false;
-
- boolean allowed = false;
- int pathLength = path.length();
- for (int i = 0; i < mSubDirectories.length && !allowed; i++) {
- String subdir = mSubDirectories[i];
- int subdirLength = subdir.length();
- if (subdirLength < pathLength &&
- path.charAt(subdirLength) == '/' &&
- path.startsWith(subdir)) {
- allowed = true;
- }
- }
- return allowed;
- }
-
- // check to see if the path matches one of our storage subdirectories
- // returns true if we have no special subdirectories
- private boolean isStorageSubDirectory(String path) {
- if (mSubDirectories == null) return false;
- for (int i = 0; i < mSubDirectories.length; i++) {
- if (path.equals(mSubDirectories[i])) {
- return true;
- }
+ private int beginSendObject(String path, int format, int parent, int storageId) {
+ MtpStorageManager.MtpObject parentObj =
+ parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
+ if (parentObj == null) {
+ return -1;
}
- return false;
- }
- // returns true if the path is in the storage root
- private boolean inStorageRoot(String path) {
- try {
- File f = new File(path);
- String canonical = f.getCanonicalPath();
- for (String root: mStorageMap.keySet()) {
- if (canonical.startsWith(root)) {
- return true;
- }
- }
- } catch (IOException e) {
- // ignore
- }
- return false;
+ Path objPath = Paths.get(path);
+ return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
}
- private int beginSendObject(String path, int format, int parent,
- int storageId, long size, long modified) {
- // if the path is outside of the storage root, do not allow access
- if (!inStorageRoot(path)) {
- Log.e(TAG, "attempt to put file outside of storage area: " + path);
- return -1;
+ private void endSendObject(int handle, boolean succeeded) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null || !mManager.endSendObject(obj, succeeded)) {
+ Log.e(TAG, "Failed to successfully end send object");
+ return;
}
- // if mSubDirectories is not null, do not allow copying files to any other locations
- if (!inStorageSubDirectory(path)) return -1;
-
- // make sure the object does not exist
- if (path != null) {
- Cursor c = null;
+ // Add the new file to MediaProvider
+ if (succeeded) {
+ String path = obj.getPath().toString();
+ int format = obj.getFormat();
+ // Get parent info from MediaProvider, since the id is different from MTP's
+ ContentValues values = new ContentValues();
+ values.put(Files.FileColumns.DATA, path);
+ values.put(Files.FileColumns.FORMAT, format);
+ values.put(Files.FileColumns.SIZE, obj.getSize());
+ values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
try {
- c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
- new String[] { path }, null, null);
- if (c != null && c.getCount() > 0) {
- Log.w(TAG, "file already exists in beginSendObject: " + path);
- return -1;
+ if (obj.getParent().isRoot()) {
+ values.put(Files.FileColumns.PARENT, 0);
+ } else {
+ int parentId = findInMedia(obj.getParent().getPath());
+ if (parentId != -1) {
+ values.put(Files.FileColumns.PARENT, parentId);
+ } else {
+ // The parent isn't in MediaProvider. Don't add the new file.
+ return;
+ }
+ }
+
+ Uri uri = mMediaProvider.insert(mObjectsUri, values);
+ if (uri != null) {
+ rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in beginSendObject", e);
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- mDatabaseModified = true;
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns.DATA, path);
- values.put(Files.FileColumns.FORMAT, format);
- values.put(Files.FileColumns.PARENT, parent);
- values.put(Files.FileColumns.STORAGE_ID, storageId);
- values.put(Files.FileColumns.SIZE, size);
- values.put(Files.FileColumns.DATE_MODIFIED, modified);
-
- try {
- Uri uri = mMediaProvider.insert(mObjectsUri, values);
- if (uri != null) {
- return Integer.parseInt(uri.getPathSegments().get(2));
- } else {
- return -1;
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in beginSendObject", e);
- return -1;
}
}
- private void endSendObject(String path, int handle, int format, boolean succeeded) {
- if (succeeded) {
- // handle abstract playlists separately
- // they do not exist in the file system so don't use the media scanner here
- if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
- // extract name from path
- String name = path;
- int lastSlash = name.lastIndexOf('/');
- if (lastSlash >= 0) {
- name = name.substring(lastSlash + 1);
- }
- // strip trailing ".pla" from the name
- if (name.endsWith(".pla")) {
- name = name.substring(0, name.length() - 4);
- }
+ private void rescanFile(String path, int handle, int format) {
+ // handle abstract playlists separately
+ // they do not exist in the file system so don't use the media scanner here
+ if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
+ // extract name from path
+ String name = path;
+ int lastSlash = name.lastIndexOf('/');
+ if (lastSlash >= 0) {
+ name = name.substring(lastSlash + 1);
+ }
+ // strip trailing ".pla" from the name
+ if (name.endsWith(".pla")) {
+ name = name.substring(0, name.length() - 4);
+ }
- ContentValues values = new ContentValues(1);
- values.put(Audio.Playlists.DATA, path);
- values.put(Audio.Playlists.NAME, name);
- values.put(Files.FileColumns.FORMAT, format);
- values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
- values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
- try {
- Uri uri = mMediaProvider.insert(
- Audio.Playlists.EXTERNAL_CONTENT_URI, values);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in endSendObject", e);
- }
- } else {
- mMediaScanner.scanMtpFile(path, handle, format);
+ ContentValues values = new ContentValues(1);
+ values.put(Audio.Playlists.DATA, path);
+ values.put(Audio.Playlists.NAME, name);
+ values.put(Files.FileColumns.FORMAT, format);
+ values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
+ values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
+ try {
+ mMediaProvider.insert(
+ Audio.Playlists.EXTERNAL_CONTENT_URI, values);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in endSendObject", e);
}
} else {
- deleteFile(handle);
+ mMediaScanner.scanMtpFile(path, handle, format);
}
}
- private void doScanDirectory(String path) {
- String[] scanPath;
- scanPath = new String[] { path };
- mMediaScanner.scanDirectories(scanPath);
+ private int[] getObjectList(int storageID, int format, int parent) {
+ Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
+ format, storageID);
+ if (objectStream == null) {
+ return null;
+ }
+ return objectStream.mapToInt(MtpStorageManager.MtpObject::getId).toArray();
}
- private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
- String where;
- String[] whereArgs;
-
- if (storageID == 0xFFFFFFFF) {
- // query all stores
- if (format == 0) {
- // query all formats
- if (parent == 0) {
- // query all objects
- where = null;
- whereArgs = null;
- } else {
- if (parent == 0xFFFFFFFF) {
- // all objects in root of store
- parent = 0;
- }
- where = PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(parent) };
- }
- } else {
- // query specific format
- if (parent == 0) {
- // query all objects
- where = FORMAT_WHERE;
- whereArgs = new String[] { Integer.toString(format) };
- } else {
- if (parent == 0xFFFFFFFF) {
- // all objects in root of store
- parent = 0;
- }
- where = FORMAT_PARENT_WHERE;
- whereArgs = new String[] { Integer.toString(format),
- Integer.toString(parent) };
- }
- }
- } else {
- // query specific store
- if (format == 0) {
- // query all formats
- if (parent == 0) {
- // query all objects
- where = STORAGE_WHERE;
- whereArgs = new String[] { Integer.toString(storageID) };
- } else {
- if (parent == 0xFFFFFFFF) {
- // all objects in root of store
- parent = 0;
- where = STORAGE_PARENT_WHERE;
- whereArgs = new String[]{Integer.toString(storageID),
- Integer.toString(parent)};
- } else {
- // If a parent is specified, the storage is redundant
- where = PARENT_WHERE;
- whereArgs = new String[]{Integer.toString(parent)};
- }
- }
- } else {
- // query specific format
- if (parent == 0) {
- // query all objects
- where = STORAGE_FORMAT_WHERE;
- whereArgs = new String[] { Integer.toString(storageID),
- Integer.toString(format) };
- } else {
- if (parent == 0xFFFFFFFF) {
- // all objects in root of store
- parent = 0;
- where = STORAGE_FORMAT_PARENT_WHERE;
- whereArgs = new String[]{Integer.toString(storageID),
- Integer.toString(format),
- Integer.toString(parent)};
- } else {
- // If a parent is specified, the storage is redundant
- where = FORMAT_PARENT_WHERE;
- whereArgs = new String[]{Integer.toString(format),
- Integer.toString(parent)};
- }
- }
- }
+ private int getNumObjects(int storageID, int format, int parent) {
+ Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent,
+ format, storageID);
+ if (objectStream == null) {
+ return -1;
}
+ return (int) objectStream.count();
+ }
- // if we are restricting queries to mSubDirectories, we need to add the restriction
- // onto our "where" arguments
- if (mSubDirectoriesWhere != null) {
- if (where == null) {
- where = mSubDirectoriesWhere;
- whereArgs = mSubDirectoriesWhereArgs;
- } else {
- where = where + " AND " + mSubDirectoriesWhere;
-
- // create new array to hold whereArgs and mSubDirectoriesWhereArgs
- String[] newWhereArgs =
- new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
- int i, j;
- for (i = 0; i < whereArgs.length; i++) {
- newWhereArgs[i] = whereArgs[i];
- }
- for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
- newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
- }
- whereArgs = newWhereArgs;
+ private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
+ int groupCode, int depth) {
+ // FIXME - implement group support
+ if (property == 0) {
+ if (groupCode == 0) {
+ return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED);
}
+ return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
}
-
- return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
- whereArgs, null, null);
- }
-
- private int[] getObjectList(int storageID, int format, int parent) {
- Cursor c = null;
- try {
- c = createObjectQuery(storageID, format, parent);
- if (c == null) {
- return null;
+ if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) {
+ // request all objects starting at root
+ handle = 0xFFFFFFFF;
+ depth = 0;
+ }
+ if (!(depth == 0 || depth == 1)) {
+ // we only support depth 0 and 1
+ // depth 0: single object, depth 1: immediate children
+ return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED);
+ }
+ Stream<MtpStorageManager.MtpObject> objectStream = Stream.of();
+ if (handle == 0xFFFFFFFF) {
+ // All objects are requested
+ objectStream = mManager.getObjects(0, format, 0xFFFFFFFF);
+ if (objectStream == null) {
+ return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
}
- int count = c.getCount();
- if (count > 0) {
- int[] result = new int[count];
- for (int i = 0; i < count; i++) {
- c.moveToNext();
- result[i] = c.getInt(0);
- }
- return result;
+ } else if (handle != 0) {
+ // Add the requested object if format matches
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectList", e);
- } finally {
- if (c != null) {
- c.close();
+ if (obj.getFormat() == format || format == 0) {
+ objectStream = Stream.of(obj);
}
}
- return null;
- }
-
- private int getNumObjects(int storageID, int format, int parent) {
- Cursor c = null;
- try {
- c = createObjectQuery(storageID, format, parent);
- if (c != null) {
- return c.getCount();
+ if (handle == 0 || depth == 1) {
+ if (handle == 0) {
+ handle = 0xFFFFFFFF;
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getNumObjects", e);
- } finally {
- if (c != null) {
- c.close();
+ // Get the direct children of root or this object.
+ Stream<MtpStorageManager.MtpObject> childStream = mManager.getObjects(handle, format,
+ 0xFFFFFFFF);
+ if (childStream == null) {
+ return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
}
- }
- return -1;
- }
-
- private int[] getSupportedPlaybackFormats() {
- return new int[] {
- // allow transfering arbitrary files
- MtpConstants.FORMAT_UNDEFINED,
-
- MtpConstants.FORMAT_ASSOCIATION,
- MtpConstants.FORMAT_TEXT,
- MtpConstants.FORMAT_HTML,
- MtpConstants.FORMAT_WAV,
- MtpConstants.FORMAT_MP3,
- MtpConstants.FORMAT_MPEG,
- MtpConstants.FORMAT_EXIF_JPEG,
- MtpConstants.FORMAT_TIFF_EP,
- MtpConstants.FORMAT_BMP,
- MtpConstants.FORMAT_GIF,
- MtpConstants.FORMAT_JFIF,
- MtpConstants.FORMAT_PNG,
- MtpConstants.FORMAT_TIFF,
- MtpConstants.FORMAT_WMA,
- MtpConstants.FORMAT_OGG,
- MtpConstants.FORMAT_AAC,
- MtpConstants.FORMAT_MP4_CONTAINER,
- MtpConstants.FORMAT_MP2,
- MtpConstants.FORMAT_3GP_CONTAINER,
- MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
- MtpConstants.FORMAT_WPL_PLAYLIST,
- MtpConstants.FORMAT_M3U_PLAYLIST,
- MtpConstants.FORMAT_PLS_PLAYLIST,
- MtpConstants.FORMAT_XML_DOCUMENT,
- MtpConstants.FORMAT_FLAC,
- MtpConstants.FORMAT_DNG,
- MtpConstants.FORMAT_HEIF,
- };
- }
-
- private int[] getSupportedCaptureFormats() {
- // no capture formats yet
- return null;
- }
-
- static final int[] FILE_PROPERTIES = {
- // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES
- // and IMAGE_PROPERTIES below
- 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,
- };
-
- static final int[] AUDIO_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,
-
- // 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,
- MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
- MtpConstants.PROPERTY_BITRATE_TYPE,
- MtpConstants.PROPERTY_AUDIO_BITRATE,
- MtpConstants.PROPERTY_NUMBER_OF_CHANNELS,
- MtpConstants.PROPERTY_SAMPLE_RATE,
- };
-
- static final int[] VIDEO_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,
-
- // video specific properties
- MtpConstants.PROPERTY_ARTIST,
- MtpConstants.PROPERTY_ALBUM_NAME,
- MtpConstants.PROPERTY_DURATION,
- MtpConstants.PROPERTY_DESCRIPTION,
- };
-
- static final int[] IMAGE_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,
- };
-
- private int[] getSupportedObjectProperties(int format) {
- switch (format) {
- case MtpConstants.FORMAT_MP3:
- case MtpConstants.FORMAT_WAV:
- case MtpConstants.FORMAT_WMA:
- case MtpConstants.FORMAT_OGG:
- case MtpConstants.FORMAT_AAC:
- return AUDIO_PROPERTIES;
- case MtpConstants.FORMAT_MPEG:
- case MtpConstants.FORMAT_3GP_CONTAINER:
- case MtpConstants.FORMAT_WMV:
- return VIDEO_PROPERTIES;
- case MtpConstants.FORMAT_EXIF_JPEG:
- case MtpConstants.FORMAT_GIF:
- case MtpConstants.FORMAT_PNG:
- case MtpConstants.FORMAT_BMP:
- case MtpConstants.FORMAT_DNG:
- case MtpConstants.FORMAT_HEIF:
- return IMAGE_PROPERTIES;
- default:
- return FILE_PROPERTIES;
- }
- }
-
- private int[] getSupportedDeviceProperties() {
- return new int[] {
- MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER,
- MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME,
- MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE,
- MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL,
- MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
- };
- }
-
- private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
- int groupCode, int depth) {
- // FIXME - implement group support
- if (groupCode != 0) {
- return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED);
+ objectStream = Stream.concat(objectStream, childStream);
}
+ MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK);
MtpPropertyGroup propertyGroup;
- if (property == 0xffffffff) {
- if (format == 0 && handle != 0 && handle != 0xffffffff) {
- // return properties based on the object's format
- format = getObjectFormat(handle);
- }
- propertyGroup = mPropertyGroupsByFormat.get(format);
- if (propertyGroup == null) {
- int[] propertyList = getSupportedObjectProperties(format);
- propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
- mVolumeName, propertyList);
- mPropertyGroupsByFormat.put(format, propertyGroup);
+ Iterator<MtpStorageManager.MtpObject> iter = objectStream.iterator();
+ while (iter.hasNext()) {
+ MtpStorageManager.MtpObject obj = iter.next();
+ if (property == 0xffffffff) {
+ // Get all properties supported by this object
+ propertyGroup = mPropertyGroupsByFormat.get(obj.getFormat());
+ if (propertyGroup == null) {
+ int[] propertyList = getSupportedObjectProperties(format);
+ propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
+ propertyList);
+ mPropertyGroupsByFormat.put(format, propertyGroup);
+ }
+ } else {
+ // Get this property value
+ final int[] propertyList = new int[]{property};
+ propertyGroup = mPropertyGroupsByProperty.get(property);
+ if (propertyGroup == null) {
+ propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
+ propertyList);
+ mPropertyGroupsByProperty.put(property, propertyGroup);
+ }
}
- } else {
- propertyGroup = mPropertyGroupsByProperty.get(property);
- if (propertyGroup == null) {
- final int[] propertyList = new int[] { property };
- propertyGroup = new MtpPropertyGroup(
- this, mMediaProvider, mVolumeName, propertyList);
- mPropertyGroupsByProperty.put(property, propertyGroup);
+ int err = propertyGroup.getPropertyList(obj, ret);
+ if (err != MtpConstants.RESPONSE_OK) {
+ return new MtpPropertyList(err);
}
}
-
- return propertyGroup.getPropertyList(handle, format, depth);
+ return ret;
}
private int renameFile(int handle, String newName) {
- Cursor c = null;
-
- // first compute current path
- String path = null;
- String[] whereArgs = new String[] { Integer.toString(handle) };
- try {
- c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
- whereArgs, null, null);
- if (c != null && c.moveToNext()) {
- path = c.getString(1);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectFilePath", e);
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- } finally {
- if (c != null) {
- c.close();
- }
- }
- if (path == null) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
}
-
- // do not allow renaming any of the special subdirectories
- if (isStorageSubDirectory(path)) {
- return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
- }
+ Path oldPath = obj.getPath();
// now rename the file. make sure this succeeds before updating database
- File oldFile = new File(path);
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash <= 1) {
+ if (!mManager.beginRenameObject(obj, newName))
return MtpConstants.RESPONSE_GENERAL_ERROR;
+ Path newPath = obj.getPath();
+ boolean success = oldPath.toFile().renameTo(newPath.toFile());
+ if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) {
+ Log.e(TAG, "Failed to end rename object");
}
- String newPath = path.substring(0, lastSlash + 1) + newName;
- File newFile = new File(newPath);
- boolean success = oldFile.renameTo(newFile);
if (!success) {
- Log.w(TAG, "renaming "+ path + " to " + newPath + " failed");
return MtpConstants.RESPONSE_GENERAL_ERROR;
}
- // finally update database
+ // finally update MediaProvider
ContentValues values = new ContentValues();
- values.put(Files.FileColumns.DATA, newPath);
- int updated = 0;
+ values.put(Files.FileColumns.DATA, newPath.toString());
+ String[] whereArgs = new String[]{oldPath.toString()};
try {
// note - we are relying on a special case in MediaProvider.update() to update
// the paths for all children in the case where this is a directory.
- updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+ mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
}
- if (updated == 0) {
- Log.e(TAG, "Unable to update path for " + path + " to " + newPath);
- // this shouldn't happen, but if it does we need to rename the file to its original name
- newFile.renameTo(oldFile);
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- }
// check if nomedia status changed
- if (newFile.isDirectory()) {
+ if (obj.isDir()) {
// for directories, check if renamed from something hidden to something non-hidden
- if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
+ if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
// directory was unhidden
try {
- mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
+ mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
} catch (RemoteException e) {
Log.e(TAG, "failed to unhide/rescan for " + newPath);
}
}
} else {
// for files, check if renamed from .nomedia to something else
- if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
- && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
+ if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
+ && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
try {
- mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
+ mMediaProvider.call(MediaStore.UNHIDE_CALL,
+ oldPath.getParent().toString(), null);
} catch (RemoteException e) {
Log.e(TAG, "failed to unhide/rescan for " + newPath);
}
}
}
-
return MtpConstants.RESPONSE_OK;
}
- private int moveObject(int handle, int newParent, int newStorage, String newPath) {
- String[] whereArgs = new String[] { Integer.toString(handle) };
+ private int beginMoveObject(int handle, int newParent, int newStorage) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ MtpStorageManager.MtpObject parent = newParent == 0 ?
+ mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+ if (obj == null || parent == null)
+ return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+
+ boolean allowed = mManager.beginMoveObject(obj, parent);
+ return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
+ }
- // do not allow renaming any of the special subdirectories
- if (isStorageSubDirectory(newPath)) {
- return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+ private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
+ int objId, boolean success) {
+ MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
+ mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent);
+ MtpStorageManager.MtpObject newParentObj = newParent == 0 ?
+ mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+ MtpStorageManager.MtpObject obj = mManager.getObject(objId);
+ String name = obj.getName();
+ if (newParentObj == null || oldParentObj == null
+ ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) {
+ Log.e(TAG, "Failed to end move object");
+ return;
}
- // update database
+ obj = mManager.getObject(objId);
+ if (!success || obj == null)
+ return;
+ // Get parent info from MediaProvider, since the id is different from MTP's
ContentValues values = new ContentValues();
- values.put(Files.FileColumns.DATA, newPath);
- values.put(Files.FileColumns.PARENT, newParent);
- values.put(Files.FileColumns.STORAGE_ID, newStorage);
- int updated = 0;
+ Path path = newParentObj.getPath().resolve(name);
+ Path oldPath = oldParentObj.getPath().resolve(name);
+ values.put(Files.FileColumns.DATA, path.toString());
+ if (obj.getParent().isRoot()) {
+ values.put(Files.FileColumns.PARENT, 0);
+ } else {
+ int parentId = findInMedia(path.getParent());
+ if (parentId != -1) {
+ values.put(Files.FileColumns.PARENT, parentId);
+ } else {
+ // The new parent isn't in MediaProvider, so delete the object instead
+ deleteFromMedia(oldPath, obj.isDir());
+ return;
+ }
+ }
+ // update MediaProvider
+ Cursor c = null;
+ String[] whereArgs = new String[]{oldPath.toString()};
try {
- // note - we are relying on a special case in MediaProvider.update() to update
- // the paths for all children in the case where this is a directory.
- updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+ int parentId = -1;
+ if (!oldParentObj.isRoot()) {
+ parentId = findInMedia(oldPath.getParent());
+ }
+ if (oldParentObj.isRoot() || parentId != -1) {
+ // Old parent exists in MediaProvider - perform a move
+ // note - we are relying on a special case in MediaProvider.update() to update
+ // the paths for all children in the case where this is a directory.
+ mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
+ } else {
+ // Old parent doesn't exist - add the object
+ values.put(Files.FileColumns.FORMAT, obj.getFormat());
+ values.put(Files.FileColumns.SIZE, obj.getSize());
+ values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
+ Uri uri = mMediaProvider.insert(mObjectsUri, values);
+ if (uri != null) {
+ rescanFile(path.toString(),
+ Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
+ }
+ }
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in mMediaProvider.update", e);
}
- if (updated == 0) {
- Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
- return MtpConstants.RESPONSE_GENERAL_ERROR;
+ }
+
+ private int beginCopyObject(int handle, int newParent, int newStorage) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ MtpStorageManager.MtpObject parent = newParent == 0 ?
+ mManager.getStorageRoot(newStorage) : mManager.getObject(newParent);
+ if (obj == null || parent == null)
+ return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+ return mManager.beginCopyObject(obj, parent);
+ }
+
+ private void endCopyObject(int handle, boolean success) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null || !mManager.endCopyObject(obj, success)) {
+ Log.e(TAG, "Failed to end copy object");
+ return;
+ }
+ if (!success) {
+ return;
+ }
+ String path = obj.getPath().toString();
+ int format = obj.getFormat();
+ // Get parent info from MediaProvider, since the id is different from MTP's
+ ContentValues values = new ContentValues();
+ values.put(Files.FileColumns.DATA, path);
+ values.put(Files.FileColumns.FORMAT, format);
+ values.put(Files.FileColumns.SIZE, obj.getSize());
+ values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
+ try {
+ if (obj.getParent().isRoot()) {
+ values.put(Files.FileColumns.PARENT, 0);
+ } else {
+ int parentId = findInMedia(obj.getParent().getPath());
+ if (parentId != -1) {
+ values.put(Files.FileColumns.PARENT, parentId);
+ } else {
+ // The parent isn't in MediaProvider. Don't add the new file.
+ return;
+ }
+ }
+ if (obj.isDir()) {
+ mMediaScanner.scanDirectories(new String[]{path});
+ } else {
+ Uri uri = mMediaProvider.insert(mObjectsUri, values);
+ if (uri != null) {
+ rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in beginSendObject", e);
}
- return MtpConstants.RESPONSE_OK;
}
private int setObjectProperty(int handle, int property,
- long intValue, String stringValue) {
+ long intValue, String stringValue) {
switch (property) {
case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
return renameFile(handle, stringValue);
@@ -912,24 +748,23 @@ public class MtpDatabase implements AutoCloseable {
value.getChars(0, length, outStringValue, 0);
outStringValue[length] = 0;
return MtpConstants.RESPONSE_OK;
-
case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE:
// use screen size as max image size
- Display display = ((WindowManager)mContext.getSystemService(
+ Display display = ((WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
int width = display.getMaximumSizeDimension();
int height = display.getMaximumSizeDimension();
- String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
+ String imageSize = Integer.toString(width) + "x" + Integer.toString(height);
imageSize.getChars(0, imageSize.length(), outStringValue, 0);
outStringValue[imageSize.length()] = 0;
return MtpConstants.RESPONSE_OK;
-
case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
outIntValue[0] = mDeviceType;
return MtpConstants.RESPONSE_OK;
-
- // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code
-
+ case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL:
+ outIntValue[0] = mBatteryLevel;
+ outIntValue[1] = mBatteryScale;
+ return MtpConstants.RESPONSE_OK;
default:
return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
}
@@ -950,179 +785,144 @@ public class MtpDatabase implements AutoCloseable {
}
private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
- char[] outName, long[] outCreatedModified) {
- Cursor c = null;
- try {
- c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
- ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
- if (c != null && c.moveToNext()) {
- outStorageFormatParent[0] = c.getInt(1);
- outStorageFormatParent[1] = c.getInt(2);
- outStorageFormatParent[2] = c.getInt(3);
-
- // extract name from path
- String path = c.getString(4);
- int lastSlash = path.lastIndexOf('/');
- int start = (lastSlash >= 0 ? lastSlash + 1 : 0);
- int end = path.length();
- if (end - start > 255) {
- end = start + 255;
- }
- path.getChars(start, end, outName, 0);
- outName[end - start] = 0;
-
- outCreatedModified[0] = c.getLong(5);
- outCreatedModified[1] = c.getLong(6);
- // use modification date as creation date if date added is not set
- if (outCreatedModified[0] == 0) {
- outCreatedModified[0] = outCreatedModified[1];
- }
- return true;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectInfo", e);
- } finally {
- if (c != null) {
- c.close();
- }
+ char[] outName, long[] outCreatedModified) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return false;
}
- return false;
+ outStorageFormatParent[0] = obj.getStorageId();
+ outStorageFormatParent[1] = obj.getFormat();
+ outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId();
+
+ int nameLen = Integer.min(obj.getName().length(), 255);
+ obj.getName().getChars(0, nameLen, outName, 0);
+ outName[nameLen] = 0;
+
+ outCreatedModified[0] = obj.getModifiedTime();
+ outCreatedModified[1] = obj.getModifiedTime();
+ return true;
}
private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
- if (handle == 0) {
- // special case root directory
- mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0);
- outFilePath[mMediaStoragePath.length()] = 0;
- outFileLengthFormat[0] = 0;
- outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION;
- return MtpConstants.RESPONSE_OK;
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
}
- Cursor c = null;
- try {
- c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
- ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
- if (c != null && c.moveToNext()) {
- String path = c.getString(1);
- path.getChars(0, path.length(), outFilePath, 0);
- outFilePath[path.length()] = 0;
- // File transfers from device to host will likely fail if the size is incorrect.
- // So to be safe, use the actual file size here.
- outFileLengthFormat[0] = new File(path).length();
- outFileLengthFormat[1] = c.getLong(2);
- return MtpConstants.RESPONSE_OK;
- } else {
- return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectFilePath", e);
+
+ String path = obj.getPath().toString();
+ int pathLen = Integer.min(path.length(), 4096);
+ path.getChars(0, pathLen, outFilePath, 0);
+ outFilePath[pathLen] = 0;
+
+ outFileLengthFormat[0] = obj.getSize();
+ outFileLengthFormat[1] = obj.getFormat();
+ return MtpConstants.RESPONSE_OK;
+ }
+
+ private int getObjectFormat(int handle) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return -1;
+ }
+ return obj.getFormat();
+ }
+
+ private int beginDeleteObject(int handle) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+ if (!mManager.beginRemoveObject(obj)) {
return MtpConstants.RESPONSE_GENERAL_ERROR;
- } finally {
- if (c != null) {
- c.close();
- }
}
+ return MtpConstants.RESPONSE_OK;
}
- private int getObjectFormat(int handle) {
+ private void endDeleteObject(int handle, boolean success) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null) {
+ return;
+ }
+ if (!mManager.endRemoveObject(obj, success))
+ Log.e(TAG, "Failed to end remove object");
+ if (success)
+ deleteFromMedia(obj.getPath(), obj.isDir());
+ }
+
+ private int findInMedia(Path path) {
+ int ret = -1;
Cursor c = null;
try {
- c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
- ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
+ c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
+ new String[]{path.toString()}, null, null);
if (c != null && c.moveToNext()) {
- return c.getInt(1);
- } else {
- return -1;
+ ret = c.getInt(0);
}
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException in getObjectFilePath", e);
- return -1;
+ Log.e(TAG, "Error finding " + path + " in MediaProvider");
} finally {
- if (c != null) {
+ if (c != null)
c.close();
- }
}
+ return ret;
}
- private int deleteFile(int handle) {
- mDatabaseModified = true;
- String path = null;
- int format = 0;
-
- Cursor c = null;
+ private void deleteFromMedia(Path path, boolean isDir) {
try {
- c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
- ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
- if (c != null && c.moveToNext()) {
- // don't convert to media path here, since we will be matching
- // against paths in the database matching /data/media
- path = c.getString(1);
- format = c.getInt(2);
- } else {
- return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
- }
-
- if (path == null || format == 0) {
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- }
-
- // do not allow deleting any of the special subdirectories
- if (isStorageSubDirectory(path)) {
- return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
- }
-
- if (format == MtpConstants.FORMAT_ASSOCIATION) {
+ // Delete the object(s) from MediaProvider, but ignore errors.
+ if (isDir) {
// recursive case - delete all children first
- Uri uri = Files.getMtpObjectsUri(mVolumeName);
- int count = mMediaProvider.delete(uri,
- // the 'like' makes it use the index, the 'lower()' makes it correct
- // when the path contains sqlite wildcard characters
- "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
- new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"});
+ mMediaProvider.delete(mObjectsUri,
+ // the 'like' makes it use the index, the 'lower()' makes it correct
+ // when the path contains sqlite wildcard characters
+ "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+ new String[]{path + "/%", Integer.toString(path.toString().length() + 1),
+ path.toString() + "/"});
}
- Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
- if (mMediaProvider.delete(uri, null, null) > 0) {
- if (format != MtpConstants.FORMAT_ASSOCIATION
- && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
+ String[] whereArgs = new String[]{path.toString()};
+ if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
+ if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
try {
- String parentPath = path.substring(0, path.lastIndexOf("/"));
+ String parentPath = path.getParent().toString();
mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
} catch (RemoteException e) {
Log.e(TAG, "failed to unhide/rescan for " + path);
}
}
- return MtpConstants.RESPONSE_OK;
} else {
- return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in deleteFile", e);
- return MtpConstants.RESPONSE_GENERAL_ERROR;
- } finally {
- if (c != null) {
- c.close();
+ Log.i(TAG, "Mediaprovider didn't delete " + path);
}
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
}
}
private int[] getObjectReferences(int handle) {
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null)
+ return null;
+ // Translate this handle to the MediaProvider Handle
+ handle = findInMedia(obj.getPath());
+ if (handle == -1)
+ return null;
Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
Cursor c = null;
try {
- c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
+ c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
if (c == null) {
return null;
}
- int count = c.getCount();
- if (count > 0) {
- int[] result = new int[count];
- for (int i = 0; i < count; i++) {
- c.moveToNext();
- result[i] = c.getInt(0);
+ ArrayList<Integer> result = new ArrayList<>();
+ while (c.moveToNext()) {
+ // Translate result handles back into handles for this session.
+ String refPath = c.getString(0);
+ MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath);
+ if (refObj != null) {
+ result.add(refObj.getId());
+ }
}
- return result;
- }
+ return result.stream().mapToInt(Integer::intValue).toArray();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in getObjectList", e);
} finally {
@@ -1134,17 +934,29 @@ public class MtpDatabase implements AutoCloseable {
}
private int setObjectReferences(int handle, int[] references) {
- mDatabaseModified = true;
+ MtpStorageManager.MtpObject obj = mManager.getObject(handle);
+ if (obj == null)
+ return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
+ // Translate this handle to the MediaProvider Handle
+ handle = findInMedia(obj.getPath());
+ if (handle == -1)
+ return MtpConstants.RESPONSE_GENERAL_ERROR;
Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
- int count = references.length;
- ContentValues[] valuesList = new ContentValues[count];
- for (int i = 0; i < count; i++) {
+ ArrayList<ContentValues> valuesList = new ArrayList<>();
+ for (int id : references) {
+ // Translate each reference id to the MediaProvider Id
+ MtpStorageManager.MtpObject refObj = mManager.getObject(id);
+ if (refObj == null)
+ continue;
+ int refHandle = findInMedia(refObj.getPath());
+ if (refHandle == -1)
+ continue;
ContentValues values = new ContentValues();
- values.put(Files.FileColumns._ID, references[i]);
- valuesList[i] = values;
+ values.put(Files.FileColumns._ID, refHandle);
+ valuesList.add(values);
}
try {
- if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
+ if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) {
return MtpConstants.RESPONSE_OK;
}
} catch (RemoteException e) {
@@ -1153,17 +965,6 @@ public class MtpDatabase implements AutoCloseable {
return MtpConstants.RESPONSE_GENERAL_ERROR;
}
- private void sessionStarted() {
- mDatabaseModified = false;
- }
-
- private void sessionEnded() {
- if (mDatabaseModified) {
- mUserContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
- mDatabaseModified = false;
- }
- }
-
// used by the JNI code
private long mNativeContext;
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index dea300838385..77d0f34f1ad6 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -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) {
- 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;
- }
- }
- } 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;
- }
- }
- }
-
+ /**
+ * 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 {
- // 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 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");
}
}
-
- 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);
- 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);
+ 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_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);
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index f9bc603e3de0..ede90dac517c 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -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;
-
- // 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
+ private List<String> mStringValues;
+
+ // 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 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 void setResult(int result) {
- mResult = result;
+ public String[] getStringValues() {
+ return mStringValues.toArray(new String[0]);
}
}
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index 6ca442c7e66f..c72b827d8a2d 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -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();
}
@@ -72,16 +70,6 @@ public class MtpStorage {
}
/**
- * 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.
*
* @return is removable
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
new file mode 100644
index 000000000000..bdc87413288a
--- /dev/null
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -0,0 +1,1210 @@
+/*
+ * Copyright (C) 2017 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.mtp;
+
+import android.media.MediaFile;
+import android.os.FileObserver;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+
+/**
+ * MtpStorageManager provides functionality for listing, tracking, and notifying MtpServer of
+ * filesystem changes. As directories are listed, this class will cache the results,
+ * and send events when objects are added/removed from cached directories.
+ * {@hide}
+ */
+public class MtpStorageManager {
+ private static final String TAG = MtpStorageManager.class.getSimpleName();
+ public static boolean sDebug = false;
+
+ // Inotify flags not provided by FileObserver
+ private static final int IN_ONLYDIR = 0x01000000;
+ private static final int IN_Q_OVERFLOW = 0x00004000;
+ private static final int IN_IGNORED = 0x00008000;
+ private static final int IN_ISDIR = 0x40000000;
+
+ private class MtpObjectObserver extends FileObserver {
+ MtpObject mObject;
+
+ MtpObjectObserver(MtpObject object) {
+ super(object.getPath().toString(),
+ MOVED_FROM | MOVED_TO | DELETE | CREATE | IN_ONLYDIR);
+ mObject = object;
+ }
+
+ @Override
+ public void onEvent(int event, String path) {
+ synchronized (MtpStorageManager.this) {
+ if ((event & IN_Q_OVERFLOW) != 0) {
+ // We are out of space in the inotify queue.
+ Log.e(TAG, "Received Inotify overflow event!");
+ }
+ MtpObject obj = mObject.getChild(path);
+ if ((event & MOVED_TO) != 0 || (event & CREATE) != 0) {
+ if (sDebug)
+ Log.i(TAG, "Got inotify added event for " + path + " " + event);
+ handleAddedObject(mObject, path, (event & IN_ISDIR) != 0);
+ } else if ((event & MOVED_FROM) != 0 || (event & DELETE) != 0) {
+ if (obj == null) {
+ Log.w(TAG, "Object was null in event " + path);
+ return;
+ }
+ if (sDebug)
+ Log.i(TAG, "Got inotify removed event for " + path + " " + event);
+ handleRemovedObject(obj);
+ } else if ((event & IN_IGNORED) != 0) {
+ if (sDebug)
+ Log.i(TAG, "inotify for " + mObject.getPath() + " deleted");
+ if (mObject.mObserver != null)
+ mObject.mObserver.stopWatching();
+ mObject.mObserver = null;
+ } else {
+ Log.w(TAG, "Got unrecognized event " + path + " " + event);
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ // If the server shuts down and starts up again, the new server's observers can be
+ // invalidated by the finalize() calls of the previous server's observers.
+ // Hence, disable the automatic stopWatching() call in FileObserver#finalize, and
+ // always call stopWatching() manually whenever an observer should be shut down.
+ }
+ }
+
+ /**
+ * Describes how the object is being acted on, to determine how events are handled.
+ */
+ private enum MtpObjectState {
+ NORMAL,
+ FROZEN, // Object is going to be modified in this session.
+ FROZEN_ADDED, // Object was frozen, and has been added.
+ FROZEN_REMOVED, // Object was frozen, and has been removed.
+ FROZEN_ONESHOT_ADD, // Object is waiting for single add event before being unfrozen.
+ FROZEN_ONESHOT_DEL, // Object is waiting for single remove event and will then be removed.
+ }
+
+ /**
+ * Describes the current operation being done on an object. Determines whether observers are
+ * created on new folders.
+ */
+ private enum MtpOperation {
+ NONE, // Any new folders not added as part of the session are immediately observed.
+ ADD, // New folders added as part of the session are immediately observed.
+ RENAME, // Renamed or moved folders are not immediately observed.
+ COPY, // Copied folders are immediately observed iff the original was.
+ DELETE, // Exists for debugging purposes only.
+ }
+
+ /** MtpObject represents either a file or directory in an associated storage. **/
+ public static class MtpObject {
+ // null for root objects
+ private MtpObject mParent;
+
+ private String mName;
+ private int mId;
+ private MtpObjectState mState;
+ private MtpOperation mOp;
+
+ private boolean mVisited;
+ private boolean mIsDir;
+
+ // null if not a directory
+ private HashMap<String, MtpObject> mChildren;
+ // null if not both a directory and visited
+ private FileObserver mObserver;
+
+ MtpObject(String name, int id, MtpObject parent, boolean isDir) {
+ mId = id;
+ mName = name;
+ mParent = parent;
+ mObserver = null;
+ mVisited = false;
+ mState = MtpObjectState.NORMAL;
+ mIsDir = isDir;
+ mOp = MtpOperation.NONE;
+
+ mChildren = mIsDir ? new HashMap<>() : null;
+ }
+
+ /** Public methods for getting object info **/
+
+ public String getName() {
+ return mName;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public boolean isDir() {
+ return mIsDir;
+ }
+
+ public int getFormat() {
+ return mIsDir ? MtpConstants.FORMAT_ASSOCIATION : MediaFile.getFormatCode(mName, null);
+ }
+
+ public int getStorageId() {
+ return getRoot().getId();
+ }
+
+ public long getModifiedTime() {
+ return getPath().toFile().lastModified() / 1000;
+ }
+
+ public MtpObject getParent() {
+ return mParent;
+ }
+
+ public MtpObject getRoot() {
+ return isRoot() ? this : mParent.getRoot();
+ }
+
+ public long getSize() {
+ return mIsDir ? 0 : getPath().toFile().length();
+ }
+
+ public Path getPath() {
+ return isRoot() ? Paths.get(mName) : mParent.getPath().resolve(mName);
+ }
+
+ public boolean isRoot() {
+ return mParent == null;
+ }
+
+ /** For MtpStorageManager only **/
+
+ private void setName(String name) {
+ mName = name;
+ }
+
+ private void setId(int id) {
+ mId = id;
+ }
+
+ private boolean isVisited() {
+ return mVisited;
+ }
+
+ private void setParent(MtpObject parent) {
+ mParent = parent;
+ }
+
+ private void setDir(boolean dir) {
+ if (dir != mIsDir) {
+ mIsDir = dir;
+ mChildren = mIsDir ? new HashMap<>() : null;
+ }
+ }
+
+ private void setVisited(boolean visited) {
+ mVisited = visited;
+ }
+
+ private MtpObjectState getState() {
+ return mState;
+ }
+
+ private void setState(MtpObjectState state) {
+ mState = state;
+ if (mState == MtpObjectState.NORMAL)
+ mOp = MtpOperation.NONE;
+ }
+
+ private MtpOperation getOperation() {
+ return mOp;
+ }
+
+ private void setOperation(MtpOperation op) {
+ mOp = op;
+ }
+
+ private FileObserver getObserver() {
+ return mObserver;
+ }
+
+ private void setObserver(FileObserver observer) {
+ mObserver = observer;
+ }
+
+ private void addChild(MtpObject child) {
+ mChildren.put(child.getName(), child);
+ }
+
+ private MtpObject getChild(String name) {
+ return mChildren.get(name);
+ }
+
+ private Collection<MtpObject> getChildren() {
+ return mChildren.values();
+ }
+
+ private boolean exists() {
+ return getPath().toFile().exists();
+ }
+
+ private MtpObject copy(boolean recursive) {
+ MtpObject copy = new MtpObject(mName, mId, mParent, mIsDir);
+ copy.mIsDir = mIsDir;
+ copy.mVisited = mVisited;
+ copy.mState = mState;
+ copy.mChildren = mIsDir ? new HashMap<>() : null;
+ if (recursive && mIsDir) {
+ for (MtpObject child : mChildren.values()) {
+ MtpObject childCopy = child.copy(true);
+ childCopy.setParent(copy);
+ copy.addChild(childCopy);
+ }
+ }
+ return copy;
+ }
+ }
+
+ /**
+ * A class that processes generated filesystem events.
+ */
+ public static abstract class MtpNotifier {
+ /**
+ * Called when an object is added.
+ */
+ public abstract void sendObjectAdded(int id);
+
+ /**
+ * Called when an object is deleted.
+ */
+ public abstract void sendObjectRemoved(int id);
+ }
+
+ private MtpNotifier mMtpNotifier;
+
+ // A cache of MtpObjects. The objects in the cache are keyed by object id.
+ // The root object of each storage isn't in this map since they all have ObjectId 0.
+ // Instead, they can be found in mRoots keyed by storageId.
+ private HashMap<Integer, MtpObject> mObjects;
+
+ // A cache of the root MtpObject for each storage, keyed by storage id.
+ private HashMap<Integer, MtpObject> mRoots;
+
+ // Object and Storage ids are allocated incrementally and not to be reused.
+ private int mNextObjectId;
+ private int mNextStorageId;
+
+ // Special subdirectories. When set, only return objects rooted in these directories, and do
+ // not allow them to be modified.
+ private Set<String> mSubdirectories;
+
+ private volatile boolean mCheckConsistency;
+ private Thread mConsistencyThread;
+
+ public MtpStorageManager(MtpNotifier notifier, Set<String> subdirectories) {
+ mMtpNotifier = notifier;
+ mSubdirectories = subdirectories;
+ mObjects = new HashMap<>();
+ mRoots = new HashMap<>();
+ mNextObjectId = 1;
+ mNextStorageId = 1;
+
+ mCheckConsistency = false; // Set to true to turn on automatic consistency checking
+ mConsistencyThread = new Thread(() -> {
+ while (mCheckConsistency) {
+ try {
+ Thread.sleep(15 * 1000);
+ } catch (InterruptedException e) {
+ return;
+ }
+ if (MtpStorageManager.this.checkConsistency()) {
+ Log.v(TAG, "Cache is consistent");
+ } else {
+ Log.w(TAG, "Cache is not consistent");
+ }
+ }
+ });
+ if (mCheckConsistency)
+ mConsistencyThread.start();
+ }
+
+ /**
+ * Clean up resources used by the storage manager.
+ */
+ public synchronized void close() {
+ Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(),
+ mObjects.values().stream());
+
+ Iterator<MtpObject> iter = objs.iterator();
+ while (iter.hasNext()) {
+ // Close all FileObservers.
+ MtpObject obj = iter.next();
+ if (obj.getObserver() != null) {
+ obj.getObserver().stopWatching();
+ obj.setObserver(null);
+ }
+ }
+
+ // Shut down the consistency checking thread
+ if (mCheckConsistency) {
+ mCheckConsistency = false;
+ mConsistencyThread.interrupt();
+ try {
+ mConsistencyThread.join();
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Sets the special subdirectories, which are the subdirectories of root storage that queries
+ * are restricted to. Must be done before any root storages are accessed.
+ * @param subDirs Subdirectories to set, or null to reset.
+ */
+ public synchronized void setSubdirectories(Set<String> subDirs) {
+ mSubdirectories = subDirs;
+ }
+
+ /**
+ * Allocates an MTP storage id for the given volume and add it to current roots.
+ * @param volume Storage to add.
+ * @return the associated MtpStorage
+ */
+ public synchronized MtpStorage addMtpStorage(StorageVolume volume) {
+ int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1;
+ MtpObject root = new MtpObject(volume.getPath(), storageId, null, true);
+ MtpStorage storage = new MtpStorage(volume, storageId);
+ mRoots.put(storageId, root);
+ return storage;
+ }
+
+ /**
+ * Removes the given storage and all associated items from the cache.
+ * @param storage Storage to remove.
+ */
+ public synchronized void removeMtpStorage(MtpStorage storage) {
+ removeObjectFromCache(getStorageRoot(storage.getStorageId()), true, true);
+ }
+
+ /**
+ * Checks if the given object can be renamed, moved, or deleted.
+ * If there are special subdirectories, they cannot be modified.
+ * @param obj Object to check.
+ * @return Whether object can be modified.
+ */
+ private synchronized boolean isSpecialSubDir(MtpObject obj) {
+ return obj.getParent().isRoot() && mSubdirectories != null
+ && !mSubdirectories.contains(obj.getName());
+ }
+
+ /**
+ * Get the object with the specified path. Visit any necessary directories on the way.
+ * @param path Full path of the object to find.
+ * @return The desired object, or null if it cannot be found.
+ */
+ public synchronized MtpObject getByPath(String path) {
+ MtpObject obj = null;
+ for (MtpObject root : mRoots.values()) {
+ if (path.startsWith(root.getName())) {
+ obj = root;
+ path = path.substring(root.getName().length());
+ }
+ }
+ for (String name : path.split("/")) {
+ if (obj == null || !obj.isDir())
+ return null;
+ if ("".equals(name))
+ continue;
+ if (!obj.isVisited())
+ getChildren(obj);
+ obj = obj.getChild(name);
+ }
+ return obj;
+ }
+
+ /**
+ * Get the object with specified id.
+ * @param id Id of object. must not be 0 or 0xFFFFFFFF
+ * @return Object, or null if error.
+ */
+ public synchronized MtpObject getObject(int id) {
+ if (id == 0 || id == 0xFFFFFFFF) {
+ Log.w(TAG, "Can't get root storages with getObject()");
+ return null;
+ }
+ if (!mObjects.containsKey(id)) {
+ Log.w(TAG, "Id " + id + " doesn't exist");
+ return null;
+ }
+ return mObjects.get(id);
+ }
+
+ /**
+ * Get the storage with specified id.
+ * @param id Storage id.
+ * @return Object that is the root of the storage, or null if error.
+ */
+ public MtpObject getStorageRoot(int id) {
+ if (!mRoots.containsKey(id)) {
+ Log.w(TAG, "StorageId " + id + " doesn't exist");
+ return null;
+ }
+ return mRoots.get(id);
+ }
+
+ private int getNextObjectId() {
+ int ret = mNextObjectId;
+ // Treat the id as unsigned int
+ mNextObjectId = (int) ((long) mNextObjectId + 1);
+ return ret;
+ }
+
+ private int getNextStorageId() {
+ return mNextStorageId++;
+ }
+
+ /**
+ * Get all objects matching the given parent, format, and storage
+ * @param parent object id of the parent. 0 for all objects, 0xFFFFFFFF for all object in root
+ * @param format format of returned objects. 0 for any format
+ * @param storageId storage id to look in. 0xFFFFFFFF for all storages
+ * @return A stream of matched objects, or null if error
+ */
+ public synchronized Stream<MtpObject> getObjects(int parent, int format, int storageId) {
+ boolean recursive = parent == 0;
+ if (parent == 0xFFFFFFFF)
+ parent = 0;
+ if (storageId == 0xFFFFFFFF) {
+ // query all stores
+ if (parent == 0) {
+ // Get the objects of this format and parent in each store.
+ ArrayList<Stream<MtpObject>> streamList = new ArrayList<>();
+ for (MtpObject root : mRoots.values()) {
+ streamList.add(getObjects(root, format, recursive));
+ }
+ return Stream.of(streamList).flatMap(Collection::stream).reduce(Stream::concat)
+ .orElseGet(Stream::empty);
+ }
+ }
+ MtpObject obj = parent == 0 ? getStorageRoot(storageId) : getObject(parent);
+ if (obj == null)
+ return null;
+ return getObjects(obj, format, recursive);
+ }
+
+ private synchronized Stream<MtpObject> getObjects(MtpObject parent, int format, boolean rec) {
+ Collection<MtpObject> children = getChildren(parent);
+ if (children == null)
+ return null;
+ Stream<MtpObject> ret = Stream.of(children).flatMap(Collection::stream);
+
+ if (format != 0) {
+ ret = ret.filter(o -> o.getFormat() == format);
+ }
+ if (rec) {
+ // Get all objects recursively.
+ ArrayList<Stream<MtpObject>> streamList = new ArrayList<>();
+ streamList.add(ret);
+ for (MtpObject o : children) {
+ if (o.isDir())
+ streamList.add(getObjects(o, format, true));
+ }
+ ret = Stream.of(streamList).filter(Objects::nonNull).flatMap(Collection::stream)
+ .reduce(Stream::concat).orElseGet(Stream::empty);
+ }
+ return ret;
+ }
+
+ /**
+ * Return the children of the given object. If the object hasn't been visited yet, add
+ * its children to the cache and start observing it.
+ * @param object the parent object
+ * @return The collection of child objects or null if error
+ */
+ private synchronized Collection<MtpObject> getChildren(MtpObject object) {
+ if (object == null || !object.isDir()) {
+ Log.w(TAG, "Can't find children of " + (object == null ? "null" : object.getId()));
+ return null;
+ }
+ if (!object.isVisited()) {
+ Path dir = object.getPath();
+ /*
+ * If a file is added after the observer starts watching the directory, but before
+ * the contents are listed, it will generate an event that will get processed
+ * after this synchronized function returns. We handle this by ignoring object
+ * added events if an object at that path already exists.
+ */
+ if (object.getObserver() != null)
+ Log.e(TAG, "Observer is not null!");
+ object.setObserver(new MtpObjectObserver(object));
+ object.getObserver().startWatching();
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
+ for (Path file : stream) {
+ addObjectToCache(object, file.getFileName().toString(),
+ file.toFile().isDirectory());
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ Log.e(TAG, e.toString());
+ object.getObserver().stopWatching();
+ object.setObserver(null);
+ return null;
+ }
+ object.setVisited(true);
+ }
+ return object.getChildren();
+ }
+
+ /**
+ * Create a new object from the given path and add it to the cache.
+ * @param parent The parent object
+ * @param newName Path of the new object
+ * @return the new object if success, else null
+ */
+ private synchronized MtpObject addObjectToCache(MtpObject parent, String newName,
+ boolean isDir) {
+ if (!parent.isRoot() && getObject(parent.getId()) != parent)
+ // parent object has been removed
+ return null;
+ if (parent.getChild(newName) != null) {
+ // Object already exists
+ return null;
+ }
+ if (mSubdirectories != null && parent.isRoot() && !mSubdirectories.contains(newName)) {
+ // Not one of the restricted subdirectories.
+ return null;
+ }
+
+ MtpObject obj = new MtpObject(newName, getNextObjectId(), parent, isDir);
+ mObjects.put(obj.getId(), obj);
+ parent.addChild(obj);
+ return obj;
+ }
+
+ /**
+ * Remove the given path from the cache.
+ * @param removed The removed object
+ * @param removeGlobal Whether to remove the object from the global id map
+ * @param recursive Whether to also remove its children recursively.
+ * @return true if successfully removed
+ */
+ private synchronized boolean removeObjectFromCache(MtpObject removed, boolean removeGlobal,
+ boolean recursive) {
+ boolean ret = removed.isRoot()
+ || removed.getParent().mChildren.remove(removed.getName(), removed);
+ if (!ret && sDebug)
+ Log.w(TAG, "Failed to remove from parent " + removed.getPath());
+ if (removed.isRoot()) {
+ ret = mRoots.remove(removed.getId(), removed) && ret;
+ } else if (removeGlobal) {
+ ret = mObjects.remove(removed.getId(), removed) && ret;
+ }
+ if (!ret && sDebug)
+ Log.w(TAG, "Failed to remove from global cache " + removed.getPath());
+ if (removed.getObserver() != null) {
+ removed.getObserver().stopWatching();
+ removed.setObserver(null);
+ }
+ if (removed.isDir() && recursive) {
+ // Remove all descendants from cache recursively
+ Collection<MtpObject> children = new ArrayList<>(removed.getChildren());
+ for (MtpObject child : children) {
+ ret = removeObjectFromCache(child, removeGlobal, true) && ret;
+ }
+ }
+ return ret;
+ }
+
+ private synchronized void handleAddedObject(MtpObject parent, String path, boolean isDir) {
+ MtpOperation op = MtpOperation.NONE;
+ MtpObject obj = parent.getChild(path);
+ if (obj != null) {
+ MtpObjectState state = obj.getState();
+ op = obj.getOperation();
+ if (obj.isDir() != isDir && state != MtpObjectState.FROZEN_REMOVED)
+ Log.d(TAG, "Inconsistent directory info! " + obj.getPath());
+ obj.setDir(isDir);
+ switch (state) {
+ case FROZEN:
+ case FROZEN_REMOVED:
+ obj.setState(MtpObjectState.FROZEN_ADDED);
+ break;
+ case FROZEN_ONESHOT_ADD:
+ obj.setState(MtpObjectState.NORMAL);
+ break;
+ case NORMAL:
+ case FROZEN_ADDED:
+ // This can happen when handling listed object in a new directory.
+ return;
+ default:
+ Log.w(TAG, "Unexpected state in add " + path + " " + state);
+ }
+ if (sDebug)
+ Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op);
+ } else {
+ obj = MtpStorageManager.this.addObjectToCache(parent, path, isDir);
+ if (obj != null) {
+ MtpStorageManager.this.mMtpNotifier.sendObjectAdded(obj.getId());
+ } else {
+ if (sDebug)
+ Log.w(TAG, "object " + path + " already exists");
+ return;
+ }
+ }
+ if (isDir) {
+ // If this was added as part of a rename do not visit or send events.
+ if (op == MtpOperation.RENAME)
+ return;
+
+ // If it was part of a copy operation, then only add observer if it was visited before.
+ if (op == MtpOperation.COPY && !obj.isVisited())
+ return;
+
+ if (obj.getObserver() != null) {
+ Log.e(TAG, "Observer is not null!");
+ return;
+ }
+ obj.setObserver(new MtpObjectObserver(obj));
+ obj.getObserver().startWatching();
+ obj.setVisited(true);
+
+ // It's possible that objects were added to a watched directory before the watch can be
+ // created, so manually handle those.
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) {
+ for (Path file : stream) {
+ if (sDebug)
+ Log.i(TAG, "Manually handling event for " + file.getFileName().toString());
+ handleAddedObject(obj, file.getFileName().toString(),
+ file.toFile().isDirectory());
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ Log.e(TAG, e.toString());
+ obj.getObserver().stopWatching();
+ obj.setObserver(null);
+ }
+ }
+ }
+
+ private synchronized void handleRemovedObject(MtpObject obj) {
+ MtpObjectState state = obj.getState();
+ MtpOperation op = obj.getOperation();
+ switch (state) {
+ case FROZEN_ADDED:
+ obj.setState(MtpObjectState.FROZEN_REMOVED);
+ break;
+ case FROZEN_ONESHOT_DEL:
+ removeObjectFromCache(obj, op != MtpOperation.RENAME, false);
+ break;
+ case FROZEN:
+ obj.setState(MtpObjectState.FROZEN_REMOVED);
+ break;
+ case NORMAL:
+ if (MtpStorageManager.this.removeObjectFromCache(obj, true, true))
+ MtpStorageManager.this.mMtpNotifier.sendObjectRemoved(obj.getId());
+ break;
+ default:
+ // This shouldn't happen; states correspond to objects that don't exist
+ Log.e(TAG, "Got unexpected object remove for " + obj.getName());
+ }
+ if (sDebug)
+ Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op);
+ }
+
+ /**
+ * Block the caller until all events currently in the event queue have been
+ * read and processed. Used for testing purposes.
+ */
+ public void flushEvents() {
+ try {
+ // TODO make this smarter
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+
+ }
+ }
+
+ /**
+ * Dumps a representation of the cache to log.
+ */
+ public synchronized void dump() {
+ for (int key : mObjects.keySet()) {
+ MtpObject obj = mObjects.get(key);
+ Log.i(TAG, key + " | " + (obj.getParent() == null ? obj.getParent().getId() : "null")
+ + " | " + obj.getName() + " | " + (obj.isDir() ? "dir" : "obj")
+ + " | " + (obj.isVisited() ? "v" : "nv") + " | " + obj.getState());
+ }
+ }
+
+ /**
+ * Checks consistency of the cache. This checks whether all objects have correct links
+ * to their parent, and whether directories are missing or have extraneous objects.
+ * @return true iff cache is consistent
+ */
+ public synchronized boolean checkConsistency() {
+ Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(),
+ mObjects.values().stream());
+ Iterator<MtpObject> iter = objs.iterator();
+ boolean ret = true;
+ while (iter.hasNext()) {
+ MtpObject obj = iter.next();
+ if (!obj.exists()) {
+ Log.w(TAG, "Object doesn't exist " + obj.getPath() + " " + obj.getId());
+ ret = false;
+ }
+ if (obj.getState() != MtpObjectState.NORMAL) {
+ Log.w(TAG, "Object " + obj.getPath() + " in state " + obj.getState());
+ ret = false;
+ }
+ if (obj.getOperation() != MtpOperation.NONE) {
+ Log.w(TAG, "Object " + obj.getPath() + " in operation " + obj.getOperation());
+ ret = false;
+ }
+ if (!obj.isRoot() && mObjects.get(obj.getId()) != obj) {
+ Log.w(TAG, "Object " + obj.getPath() + " is not in map correctly");
+ ret = false;
+ }
+ if (obj.getParent() != null) {
+ if (obj.getParent().isRoot() && obj.getParent()
+ != mRoots.get(obj.getParent().getId())) {
+ Log.w(TAG, "Root parent is not in root mapping " + obj.getPath());
+ ret = false;
+ }
+ if (!obj.getParent().isRoot() && obj.getParent()
+ != mObjects.get(obj.getParent().getId())) {
+ Log.w(TAG, "Parent is not in object mapping " + obj.getPath());
+ ret = false;
+ }
+ if (obj.getParent().getChild(obj.getName()) != obj) {
+ Log.w(TAG, "Child does not exist in parent " + obj.getPath());
+ ret = false;
+ }
+ }
+ if (obj.isDir()) {
+ if (obj.isVisited() == (obj.getObserver() == null)) {
+ Log.w(TAG, obj.getPath() + " is " + (obj.isVisited() ? "" : "not ")
+ + " visited but observer is " + obj.getObserver());
+ ret = false;
+ }
+ if (!obj.isVisited() && obj.getChildren().size() > 0) {
+ Log.w(TAG, obj.getPath() + " is not visited but has children");
+ ret = false;
+ }
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) {
+ Set<String> files = new HashSet<>();
+ for (Path file : stream) {
+ if (obj.isVisited() &&
+ obj.getChild(file.getFileName().toString()) == null &&
+ (mSubdirectories == null || !obj.isRoot() ||
+ mSubdirectories.contains(file.getFileName().toString()))) {
+ Log.w(TAG, "File exists in fs but not in children " + file);
+ ret = false;
+ }
+ files.add(file.toString());
+ }
+ for (MtpObject child : obj.getChildren()) {
+ if (!files.contains(child.getPath().toString())) {
+ Log.w(TAG, "File in children doesn't exist in fs " + child.getPath());
+ ret = false;
+ }
+ if (child != mObjects.get(child.getId())) {
+ Log.w(TAG, "Child is not in object map " + child.getPath());
+ ret = false;
+ }
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ Log.w(TAG, e.toString());
+ ret = false;
+ }
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Informs MtpStorageManager that an object with the given path is about to be added.
+ * @param parent The parent object of the object to be added.
+ * @param name Filename of object to add.
+ * @return Object id of the added object, or -1 if it cannot be added.
+ */
+ public synchronized int beginSendObject(MtpObject parent, String name, int format) {
+ if (sDebug)
+ Log.v(TAG, "beginSendObject " + name);
+ if (!parent.isDir())
+ return -1;
+ if (parent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name))
+ return -1;
+ getChildren(parent); // Ensure parent is visited
+ MtpObject obj = addObjectToCache(parent, name, format == MtpConstants.FORMAT_ASSOCIATION);
+ if (obj == null)
+ return -1;
+ obj.setState(MtpObjectState.FROZEN);
+ obj.setOperation(MtpOperation.ADD);
+ return obj.getId();
+ }
+
+ /**
+ * Clean up the object state after a sendObject operation.
+ * @param obj The object, returned from beginAddObject().
+ * @param succeeded Whether the file was successfully created.
+ * @return Whether cache state was successfully cleaned up.
+ */
+ public synchronized boolean endSendObject(MtpObject obj, boolean succeeded) {
+ if (sDebug)
+ Log.v(TAG, "endSendObject " + succeeded);
+ return generalEndAddObject(obj, succeeded, true);
+ }
+
+ /**
+ * Informs MtpStorageManager that the given object is about to be renamed.
+ * If this returns true, it must be followed with an endRenameObject()
+ * @param obj Object to be renamed.
+ * @param newName New name of the object.
+ * @return Whether renaming is allowed.
+ */
+ public synchronized boolean beginRenameObject(MtpObject obj, String newName) {
+ if (sDebug)
+ Log.v(TAG, "beginRenameObject " + obj.getName() + " " + newName);
+ if (obj.isRoot())
+ return false;
+ if (isSpecialSubDir(obj))
+ return false;
+ if (obj.getParent().getChild(newName) != null)
+ // Object already exists in parent with that name.
+ return false;
+
+ MtpObject oldObj = obj.copy(false);
+ obj.setName(newName);
+ obj.getParent().addChild(obj);
+ oldObj.getParent().addChild(oldObj);
+ return generalBeginRenameObject(oldObj, obj);
+ }
+
+ /**
+ * Cleans up cache state after a rename operation and sends any events that were missed.
+ * @param obj The object being renamed, the same one that was passed in beginRenameObject().
+ * @param oldName The previous name of the object.
+ * @param success Whether the rename operation succeeded.
+ * @return Whether state was successfully cleaned up.
+ */
+ public synchronized boolean endRenameObject(MtpObject obj, String oldName, boolean success) {
+ if (sDebug)
+ Log.v(TAG, "endRenameObject " + success);
+ MtpObject parent = obj.getParent();
+ MtpObject oldObj = parent.getChild(oldName);
+ if (!success) {
+ // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+ // Switch the objects, except for their name and state.
+ MtpObject temp = oldObj;
+ MtpObjectState oldState = oldObj.getState();
+ temp.setName(obj.getName());
+ temp.setState(obj.getState());
+ oldObj = obj;
+ oldObj.setName(oldName);
+ oldObj.setState(oldState);
+ obj = temp;
+ parent.addChild(obj);
+ parent.addChild(oldObj);
+ }
+ return generalEndRenameObject(oldObj, obj, success);
+ }
+
+ /**
+ * Informs MtpStorageManager that the given object is about to be deleted by the initiator,
+ * so don't send an event.
+ * @param obj Object to be deleted.
+ * @return Whether cache deletion is allowed.
+ */
+ public synchronized boolean beginRemoveObject(MtpObject obj) {
+ if (sDebug)
+ Log.v(TAG, "beginRemoveObject " + obj.getName());
+ return !obj.isRoot() && !isSpecialSubDir(obj)
+ && generalBeginRemoveObject(obj, MtpOperation.DELETE);
+ }
+
+ /**
+ * Clean up cache state after a delete operation and send any events that were missed.
+ * @param obj Object to be deleted, same one passed in beginRemoveObject().
+ * @param success Whether operation was completed successfully.
+ * @return Whether cache state is correct.
+ */
+ public synchronized boolean endRemoveObject(MtpObject obj, boolean success) {
+ if (sDebug)
+ Log.v(TAG, "endRemoveObject " + success);
+ boolean ret = true;
+ if (obj.isDir()) {
+ for (MtpObject child : new ArrayList<>(obj.getChildren()))
+ if (child.getOperation() == MtpOperation.DELETE)
+ ret = endRemoveObject(child, success) && ret;
+ }
+ return generalEndRemoveObject(obj, success, true) && ret;
+ }
+
+ /**
+ * Informs MtpStorageManager that the given object is about to be moved to a new parent.
+ * @param obj Object to be moved.
+ * @param newParent The new parent object.
+ * @return Whether the move is allowed.
+ */
+ public synchronized boolean beginMoveObject(MtpObject obj, MtpObject newParent) {
+ if (sDebug)
+ Log.v(TAG, "beginMoveObject " + newParent.getPath());
+ if (obj.isRoot())
+ return false;
+ if (isSpecialSubDir(obj))
+ return false;
+ getChildren(newParent); // Ensure parent is visited
+ if (newParent.getChild(obj.getName()) != null)
+ // Object already exists in parent with that name.
+ return false;
+ if (obj.getStorageId() != newParent.getStorageId()) {
+ /*
+ * The move is occurring across storages. The observers will not remain functional
+ * after the move, and the move will not be atomic. We have to copy the file tree
+ * to the destination and recreate the observers once copy is complete.
+ */
+ MtpObject newObj = obj.copy(true);
+ newObj.setParent(newParent);
+ newParent.addChild(newObj);
+ return generalBeginRemoveObject(obj, MtpOperation.RENAME)
+ && generalBeginCopyObject(newObj, false);
+ }
+ // Move obj to new parent, create a dummy object in the old parent.
+ MtpObject oldObj = obj.copy(false);
+ obj.setParent(newParent);
+ oldObj.getParent().addChild(oldObj);
+ obj.getParent().addChild(obj);
+ return generalBeginRenameObject(oldObj, obj);
+ }
+
+ /**
+ * Clean up cache state after a move operation and send any events that were missed.
+ * @param oldParent The old parent object.
+ * @param newParent The new parent object.
+ * @param name The name of the object being moved.
+ * @param success Whether operation was completed successfully.
+ * @return Whether cache state is correct.
+ */
+ public synchronized boolean endMoveObject(MtpObject oldParent, MtpObject newParent, String name,
+ boolean success) {
+ if (sDebug)
+ Log.v(TAG, "endMoveObject " + success);
+ MtpObject oldObj = oldParent.getChild(name);
+ MtpObject newObj = newParent.getChild(name);
+ if (oldObj == null || newObj == null)
+ return false;
+ if (oldParent.getStorageId() != newObj.getStorageId()) {
+ boolean ret = endRemoveObject(oldObj, success);
+ return generalEndCopyObject(newObj, success, true) && ret;
+ }
+ if (!success) {
+ // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+ // Switch the objects, except for their parent and state.
+ MtpObject temp = oldObj;
+ MtpObjectState oldState = oldObj.getState();
+ temp.setParent(newObj.getParent());
+ temp.setState(newObj.getState());
+ oldObj = newObj;
+ oldObj.setParent(oldParent);
+ oldObj.setState(oldState);
+ newObj = temp;
+ newObj.getParent().addChild(newObj);
+ oldParent.addChild(oldObj);
+ }
+ return generalEndRenameObject(oldObj, newObj, success);
+ }
+
+ /**
+ * Informs MtpStorageManager that the given object is about to be copied recursively.
+ * @param object Object to be copied
+ * @param newParent New parent for the object.
+ * @return The object id for the new copy, or -1 if error.
+ */
+ public synchronized int beginCopyObject(MtpObject object, MtpObject newParent) {
+ if (sDebug)
+ Log.v(TAG, "beginCopyObject " + object.getName() + " to " + newParent.getPath());
+ String name = object.getName();
+ if (!newParent.isDir())
+ return -1;
+ if (newParent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name))
+ return -1;
+ getChildren(newParent); // Ensure parent is visited
+ if (newParent.getChild(name) != null)
+ return -1;
+ MtpObject newObj = object.copy(object.isDir());
+ newParent.addChild(newObj);
+ newObj.setParent(newParent);
+ if (!generalBeginCopyObject(newObj, true))
+ return -1;
+ return newObj.getId();
+ }
+
+ /**
+ * Cleans up cache state after a copy operation.
+ * @param object Object that was copied.
+ * @param success Whether the operation was successful.
+ * @return Whether cache state is consistent.
+ */
+ public synchronized boolean endCopyObject(MtpObject object, boolean success) {
+ if (sDebug)
+ Log.v(TAG, "endCopyObject " + object.getName() + " " + success);
+ return generalEndCopyObject(object, success, false);
+ }
+
+ private synchronized boolean generalEndAddObject(MtpObject obj, boolean succeeded,
+ boolean removeGlobal) {
+ switch (obj.getState()) {
+ case FROZEN:
+ // Object was never created.
+ if (succeeded) {
+ // The operation was successful so the event must still be in the queue.
+ obj.setState(MtpObjectState.FROZEN_ONESHOT_ADD);
+ } else {
+ // The operation failed and never created the file.
+ if (!removeObjectFromCache(obj, removeGlobal, false)) {
+ return false;
+ }
+ }
+ break;
+ case FROZEN_ADDED:
+ obj.setState(MtpObjectState.NORMAL);
+ if (!succeeded) {
+ MtpObject parent = obj.getParent();
+ // The operation failed but some other process created the file. Send an event.
+ if (!removeObjectFromCache(obj, removeGlobal, false))
+ return false;
+ handleAddedObject(parent, obj.getName(), obj.isDir());
+ }
+ // else: The operation successfully created the object.
+ break;
+ case FROZEN_REMOVED:
+ if (!removeObjectFromCache(obj, removeGlobal, false))
+ return false;
+ if (succeeded) {
+ // Some other process deleted the object. Send an event.
+ mMtpNotifier.sendObjectRemoved(obj.getId());
+ }
+ // else: Mtp deleted the object as part of cleanup. Don't send an event.
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ private synchronized boolean generalEndRemoveObject(MtpObject obj, boolean success,
+ boolean removeGlobal) {
+ switch (obj.getState()) {
+ case FROZEN:
+ if (success) {
+ // Object was deleted successfully, and event is still in the queue.
+ obj.setState(MtpObjectState.FROZEN_ONESHOT_DEL);
+ } else {
+ // Object was not deleted.
+ obj.setState(MtpObjectState.NORMAL);
+ }
+ break;
+ case FROZEN_ADDED:
+ // Object was deleted, and then readded.
+ obj.setState(MtpObjectState.NORMAL);
+ if (success) {
+ // Some other process readded the object.
+ MtpObject parent = obj.getParent();
+ if (!removeObjectFromCache(obj, removeGlobal, false))
+ return false;
+ handleAddedObject(parent, obj.getName(), obj.isDir());
+ }
+ // else : Object still exists after failure.
+ break;
+ case FROZEN_REMOVED:
+ if (!removeObjectFromCache(obj, removeGlobal, false))
+ return false;
+ if (!success) {
+ // Some other process deleted the object.
+ mMtpNotifier.sendObjectRemoved(obj.getId());
+ }
+ // else : This process deleted the object as part of the operation.
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ private synchronized boolean generalBeginRenameObject(MtpObject fromObj, MtpObject toObj) {
+ fromObj.setState(MtpObjectState.FROZEN);
+ toObj.setState(MtpObjectState.FROZEN);
+ fromObj.setOperation(MtpOperation.RENAME);
+ toObj.setOperation(MtpOperation.RENAME);
+ return true;
+ }
+
+ private synchronized boolean generalEndRenameObject(MtpObject fromObj, MtpObject toObj,
+ boolean success) {
+ boolean ret = generalEndRemoveObject(fromObj, success, !success);
+ return generalEndAddObject(toObj, success, success) && ret;
+ }
+
+ private synchronized boolean generalBeginRemoveObject(MtpObject obj, MtpOperation op) {
+ obj.setState(MtpObjectState.FROZEN);
+ obj.setOperation(op);
+ if (obj.isDir()) {
+ for (MtpObject child : obj.getChildren())
+ generalBeginRemoveObject(child, op);
+ }
+ return true;
+ }
+
+ private synchronized boolean generalBeginCopyObject(MtpObject obj, boolean newId) {
+ obj.setState(MtpObjectState.FROZEN);
+ obj.setOperation(MtpOperation.COPY);
+ if (newId) {
+ obj.setId(getNextObjectId());
+ mObjects.put(obj.getId(), obj);
+ }
+ if (obj.isDir())
+ for (MtpObject child : obj.getChildren())
+ if (!generalBeginCopyObject(child, newId))
+ return false;
+ return true;
+ }
+
+ private synchronized boolean generalEndCopyObject(MtpObject obj, boolean success, boolean addGlobal) {
+ if (success && addGlobal)
+ mObjects.put(obj.getId(), obj);
+ boolean ret = true;
+ if (obj.isDir()) {
+ for (MtpObject child : new ArrayList<>(obj.getChildren())) {
+ if (child.getOperation() == MtpOperation.COPY)
+ ret = generalEndCopyObject(child, success, addGlobal) && ret;
+ }
+ }
+ ret = generalEndAddObject(obj, success, success || !addGlobal) && ret;
+ return ret;
+ }
+}
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 4e8c72bae9b9..23ef84f6ad90 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -19,7 +19,7 @@
#include "android_media_Utils.h"
#include "mtp.h"
-#include "MtpDatabase.h"
+#include "IMtpDatabase.h"
#include "MtpDataPacket.h"
#include "MtpObjectInfo.h"
#include "MtpProperty.h"
@@ -55,7 +55,7 @@ using namespace android;
static jmethodID method_beginSendObject;
static jmethodID method_endSendObject;
-static jmethodID method_doScanDirectory;
+static jmethodID method_rescanFile;
static jmethodID method_getObjectList;
static jmethodID method_getNumObjects;
static jmethodID method_getSupportedPlaybackFormats;
@@ -68,35 +68,34 @@ static jmethodID method_setDeviceProperty;
static jmethodID method_getObjectPropertyList;
static jmethodID method_getObjectInfo;
static jmethodID method_getObjectFilePath;
-static jmethodID method_deleteFile;
-static jmethodID method_moveObject;
+static jmethodID method_beginDeleteObject;
+static jmethodID method_endDeleteObject;
+static jmethodID method_beginMoveObject;
+static jmethodID method_endMoveObject;
+static jmethodID method_beginCopyObject;
+static jmethodID method_endCopyObject;
static jmethodID method_getObjectReferences;
static jmethodID method_setObjectReferences;
-static jmethodID method_sessionStarted;
-static jmethodID method_sessionEnded;
static jfieldID field_context;
-static jfieldID field_batteryLevel;
-static jfieldID field_batteryScale;
-static jfieldID field_deviceType;
-
-// MtpPropertyList fields
-static jfieldID field_mCount;
-static jfieldID field_mResult;
-static jfieldID field_mObjectHandles;
-static jfieldID field_mPropertyCodes;
-static jfieldID field_mDataTypes;
-static jfieldID field_mLongValues;
-static jfieldID field_mStringValues;
-
-
-MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
- return (MtpDatabase *)env->GetLongField(database, field_context);
+
+// MtpPropertyList methods
+static jmethodID method_getCode;
+static jmethodID method_getCount;
+static jmethodID method_getObjectHandles;
+static jmethodID method_getPropertyCodes;
+static jmethodID method_getDataTypes;
+static jmethodID method_getLongValues;
+static jmethodID method_getStringValues;
+
+
+IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
+ return (IMtpDatabase *)env->GetLongField(database, field_context);
}
// ----------------------------------------------------------------------------
-class MyMtpDatabase : public MtpDatabase {
+class MtpDatabase : public IMtpDatabase {
private:
jobject mDatabase;
jintArray mIntBuffer;
@@ -104,23 +103,20 @@ private:
jcharArray mStringBuffer;
public:
- MyMtpDatabase(JNIEnv *env, jobject client);
- virtual ~MyMtpDatabase();
+ MtpDatabase(JNIEnv *env, jobject client);
+ virtual ~MtpDatabase();
void cleanup(JNIEnv *env);
virtual MtpObjectHandle beginSendObject(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
- MtpStorageID storage,
- uint64_t size,
- time_t modified);
+ MtpStorageID storage);
- virtual void endSendObject(const char* path,
- MtpObjectHandle handle,
- MtpObjectFormat format,
- bool succeeded);
+ virtual void endSendObject(MtpObjectHandle handle, bool succeeded);
- virtual void doScanDirectory(const char* path);
+ virtual void rescanFile(const char* path,
+ MtpObjectHandle handle,
+ MtpObjectFormat format);
virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID,
MtpObjectFormat format,
@@ -167,7 +163,8 @@ public:
MtpString& outFilePath,
int64_t& outFileLength,
MtpObjectFormat& outFormat);
- virtual MtpResponseCode deleteFile(MtpObjectHandle handle);
+ virtual MtpResponseCode beginDeleteObject(MtpObjectHandle handle);
+ virtual void endDeleteObject(MtpObjectHandle handle, bool succeeded);
bool getObjectPropertyInfo(MtpObjectProperty property, int& type);
bool getDevicePropertyInfo(MtpDeviceProperty property, int& type);
@@ -182,12 +179,17 @@ public:
virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property);
- virtual MtpResponseCode moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
- MtpStorageID newStorage, MtpString& newPath);
+ virtual MtpResponseCode beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpStorageID newStorage);
+
+ virtual void endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent,
+ MtpStorageID oldStorage, MtpStorageID newStorage,
+ MtpObjectHandle handle, bool succeeded);
- virtual void sessionStarted();
+ virtual MtpResponseCode beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpStorageID newStorage);
+ virtual void endCopyObject(MtpObjectHandle handle, bool succeeded);
- virtual void sessionEnded();
};
// ----------------------------------------------------------------------------
@@ -202,7 +204,7 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa
// ----------------------------------------------------------------------------
-MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
+MtpDatabase::MtpDatabase(JNIEnv *env, jobject client)
: mDatabase(env->NewGlobalRef(client)),
mIntBuffer(NULL),
mLongBuffer(NULL),
@@ -228,27 +230,24 @@ MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
}
-void MyMtpDatabase::cleanup(JNIEnv *env) {
+void MtpDatabase::cleanup(JNIEnv *env) {
env->DeleteGlobalRef(mDatabase);
env->DeleteGlobalRef(mIntBuffer);
env->DeleteGlobalRef(mLongBuffer);
env->DeleteGlobalRef(mStringBuffer);
}
-MyMtpDatabase::~MyMtpDatabase() {
+MtpDatabase::~MtpDatabase() {
}
-MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path,
+MtpObjectHandle MtpDatabase::beginSendObject(const char* path,
MtpObjectFormat format,
MtpObjectHandle parent,
- MtpStorageID storage,
- uint64_t size,
- time_t modified) {
+ MtpStorageID storage) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jstring pathStr = env->NewStringUTF(path);
MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject,
- pathStr, (jint)format, (jint)parent, (jint)storage,
- (jlong)size, (jlong)modified);
+ pathStr, (jint)format, (jint)parent, (jint)storage);
if (pathStr)
env->DeleteLocalRef(pathStr);
@@ -256,29 +255,26 @@ MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path,
return result;
}
-void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle,
- MtpObjectFormat format, bool succeeded) {
+void MtpDatabase::endSendObject(MtpObjectHandle handle, bool succeeded) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- jstring pathStr = env->NewStringUTF(path);
- env->CallVoidMethod(mDatabase, method_endSendObject, pathStr,
- (jint)handle, (jint)format, (jboolean)succeeded);
+ env->CallVoidMethod(mDatabase, method_endSendObject, (jint)handle, (jboolean)succeeded);
- if (pathStr)
- env->DeleteLocalRef(pathStr);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
-void MyMtpDatabase::doScanDirectory(const char* path) {
+void MtpDatabase::rescanFile(const char* path, MtpObjectHandle handle,
+ MtpObjectFormat format) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jstring pathStr = env->NewStringUTF(path);
- env->CallVoidMethod(mDatabase, method_doScanDirectory, pathStr);
+ env->CallVoidMethod(mDatabase, method_rescanFile, pathStr,
+ (jint)handle, (jint)format);
if (pathStr)
env->DeleteLocalRef(pathStr);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
-MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID,
+MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID,
MtpObjectFormat format,
MtpObjectHandle parent) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -298,7 +294,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID,
return list;
}
-int MyMtpDatabase::getNumObjects(MtpStorageID storageID,
+int MtpDatabase::getNumObjects(MtpStorageID storageID,
MtpObjectFormat format,
MtpObjectHandle parent) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -309,7 +305,7 @@ int MyMtpDatabase::getNumObjects(MtpStorageID storageID,
return result;
}
-MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() {
+MtpObjectFormatList* MtpDatabase::getSupportedPlaybackFormats() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
method_getSupportedPlaybackFormats);
@@ -327,7 +323,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() {
return list;
}
-MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() {
+MtpObjectFormatList* MtpDatabase::getSupportedCaptureFormats() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
method_getSupportedCaptureFormats);
@@ -345,7 +341,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() {
return list;
}
-MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
+MtpObjectPropertyList* MtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
method_getSupportedObjectProperties, (jint)format);
@@ -363,7 +359,7 @@ MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectForm
return list;
}
-MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() {
+MtpDevicePropertyList* MtpDatabase::getSupportedDeviceProperties() {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
method_getSupportedDeviceProperties);
@@ -381,7 +377,7 @@ MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() {
return list;
}
-MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet) {
static_assert(sizeof(jint) >= sizeof(MtpObjectHandle),
@@ -397,42 +393,26 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
static_cast<jint>(property),
0,
0);
- MtpResponseCode result = env->GetIntField(list, field_mResult);
- int count = env->GetIntField(list, field_mCount);
- if (result == MTP_RESPONSE_OK && count != 1)
+ MtpResponseCode result = env->CallIntMethod(list, method_getCode);
+ jint count = env->CallIntMethod(list, method_getCount);
+ if (count != 1)
result = MTP_RESPONSE_GENERAL_ERROR;
if (result == MTP_RESPONSE_OK) {
- jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
- jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
- jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
- jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
- jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+ jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles);
+ jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes);
+ jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes);
+ jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues);
+ jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues);
jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
jint* dataTypes = env->GetIntArrayElements(dataTypesArray, 0);
- jlong* longValues = (longValuesArray ? env->GetLongArrayElements(longValuesArray, 0) : NULL);
+ jlong* longValues = env->GetLongArrayElements(longValuesArray, 0);
int type = dataTypes[0];
jlong longValue = (longValues ? longValues[0] : 0);
- // special case date properties, which are strings to MTP
- // but stored internally as a uint64
- if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) {
- char date[20];
- formatDateTime(longValue, date, sizeof(date));
- packet.putString(date);
- goto out;
- }
- // release date is stored internally as just the year
- if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) {
- char date[20];
- snprintf(date, sizeof(date), "%04" PRId64 "0101T000000", longValue);
- packet.putString(date);
- goto out;
- }
-
switch (type) {
case MTP_TYPE_INT8:
packet.putInt8(longValue);
@@ -481,20 +461,16 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle,
ALOGE("unsupported type in getObjectPropertyValue\n");
result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT;
}
-out:
env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
- if (longValues)
- env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+ env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
env->DeleteLocalRef(objectHandlesArray);
env->DeleteLocalRef(propertyCodesArray);
env->DeleteLocalRef(dataTypesArray);
- if (longValuesArray)
- env->DeleteLocalRef(longValuesArray);
- if (stringValuesArray)
- env->DeleteLocalRef(stringValuesArray);
+ env->DeleteLocalRef(longValuesArray);
+ env->DeleteLocalRef(stringValuesArray);
}
env->DeleteLocalRef(list);
@@ -559,7 +535,7 @@ static bool readLongValue(int type, MtpDataPacket& packet, jlong& longValue) {
return true;
}
-MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::setObjectPropertyValue(MtpObjectHandle handle,
MtpObjectProperty property,
MtpDataPacket& packet) {
int type;
@@ -590,80 +566,73 @@ fail:
return result;
}
-MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
+MtpResponseCode MtpDatabase::getDevicePropertyValue(MtpDeviceProperty property,
MtpDataPacket& packet) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
+ int type;
- if (property == MTP_DEVICE_PROPERTY_BATTERY_LEVEL) {
- // special case - implemented here instead of Java
- packet.putUInt8((uint8_t)env->GetIntField(mDatabase, field_batteryLevel));
- return MTP_RESPONSE_OK;
- } else {
- int type;
-
- if (!getDevicePropertyInfo(property, type))
- return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
+ if (!getDevicePropertyInfo(property, type))
+ return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
- jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
- (jint)property, mLongBuffer, mStringBuffer);
- if (result != MTP_RESPONSE_OK) {
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return result;
- }
+ jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+ (jint)property, mLongBuffer, mStringBuffer);
+ if (result != MTP_RESPONSE_OK) {
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
+ }
- jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
- jlong longValue = longValues[0];
- env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
+ jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
+ jlong longValue = longValues[0];
+ env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
- switch (type) {
- case MTP_TYPE_INT8:
- packet.putInt8(longValue);
- break;
- case MTP_TYPE_UINT8:
- packet.putUInt8(longValue);
- break;
- case MTP_TYPE_INT16:
- packet.putInt16(longValue);
- break;
- case MTP_TYPE_UINT16:
- packet.putUInt16(longValue);
- break;
- case MTP_TYPE_INT32:
- packet.putInt32(longValue);
- break;
- case MTP_TYPE_UINT32:
- packet.putUInt32(longValue);
- break;
- case MTP_TYPE_INT64:
- packet.putInt64(longValue);
- break;
- case MTP_TYPE_UINT64:
- packet.putUInt64(longValue);
- break;
- case MTP_TYPE_INT128:
- packet.putInt128(longValue);
- break;
- case MTP_TYPE_UINT128:
- packet.putInt128(longValue);
- break;
- case MTP_TYPE_STR:
- {
- jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
- packet.putString(str);
- env->ReleaseCharArrayElements(mStringBuffer, str, 0);
- break;
- }
- default:
- ALOGE("unsupported type in getDevicePropertyValue\n");
- return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
+ switch (type) {
+ case MTP_TYPE_INT8:
+ packet.putInt8(longValue);
+ break;
+ case MTP_TYPE_UINT8:
+ packet.putUInt8(longValue);
+ break;
+ case MTP_TYPE_INT16:
+ packet.putInt16(longValue);
+ break;
+ case MTP_TYPE_UINT16:
+ packet.putUInt16(longValue);
+ break;
+ case MTP_TYPE_INT32:
+ packet.putInt32(longValue);
+ break;
+ case MTP_TYPE_UINT32:
+ packet.putUInt32(longValue);
+ break;
+ case MTP_TYPE_INT64:
+ packet.putInt64(longValue);
+ break;
+ case MTP_TYPE_UINT64:
+ packet.putUInt64(longValue);
+ break;
+ case MTP_TYPE_INT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_UINT128:
+ packet.putInt128(longValue);
+ break;
+ case MTP_TYPE_STR:
+ {
+ jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
+ packet.putString(str);
+ env->ReleaseCharArrayElements(mStringBuffer, str, 0);
+ break;
}
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return MTP_RESPONSE_OK;
+ default:
+ ALOGE("unsupported type in getDevicePropertyValue\n");
+ return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT;
}
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return MTP_RESPONSE_OK;
}
-MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
+MtpResponseCode MtpDatabase::setDevicePropertyValue(MtpDeviceProperty property,
MtpDataPacket& packet) {
int type;
@@ -693,11 +662,11 @@ fail:
return result;
}
-MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) {
+MtpResponseCode MtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) {
return -1;
}
-MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
uint32_t format, uint32_t property,
int groupCode, int depth,
MtpDataPacket& packet) {
@@ -715,16 +684,16 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
checkAndClearExceptionFromCallback(env, __FUNCTION__);
if (!list)
return MTP_RESPONSE_GENERAL_ERROR;
- int count = env->GetIntField(list, field_mCount);
- MtpResponseCode result = env->GetIntField(list, field_mResult);
+ int count = env->CallIntMethod(list, method_getCount);
+ MtpResponseCode result = env->CallIntMethod(list, method_getCode);
packet.putUInt32(count);
if (count > 0) {
- jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles);
- jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes);
- jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes);
- jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues);
- jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues);
+ jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles);
+ jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes);
+ jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes);
+ jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues);
+ jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues);
jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0);
jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0);
@@ -781,7 +750,7 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
break;
}
default:
- ALOGE("bad or unsupported data type in MyMtpDatabase::getObjectPropertyList");
+ ALOGE("bad or unsupported data type in MtpDatabase::getObjectPropertyList");
break;
}
}
@@ -789,16 +758,13 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0);
env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0);
env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0);
- if (longValues)
- env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
+ env->ReleaseLongArrayElements(longValuesArray, longValues, 0);
env->DeleteLocalRef(objectHandlesArray);
env->DeleteLocalRef(propertyCodesArray);
env->DeleteLocalRef(dataTypesArray);
- if (longValuesArray)
- env->DeleteLocalRef(longValuesArray);
- if (stringValuesArray)
- env->DeleteLocalRef(stringValuesArray);
+ env->DeleteLocalRef(longValuesArray);
+ env->DeleteLocalRef(stringValuesArray);
}
env->DeleteLocalRef(list);
@@ -822,7 +788,7 @@ static long getLongFromExifEntry(ExifEntry *e) {
return exif_get_long(e->data, o);
}
-MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle,
MtpObjectInfo& info) {
MtpString path;
int64_t length;
@@ -914,7 +880,7 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
return MTP_RESPONSE_OK;
}
-void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
+void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
MtpString path;
int64_t length;
MtpObjectFormat format;
@@ -979,7 +945,7 @@ void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize)
return result;
}
-MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::getObjectFilePath(MtpObjectHandle handle,
MtpString& outFilePath,
int64_t& outFileLength,
MtpObjectFormat& outFormat) {
@@ -1005,26 +971,60 @@ MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
return result;
}
-MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) {
+MtpResponseCode MtpDatabase::beginDeleteObject(MtpObjectHandle handle) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- MtpResponseCode result = env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle);
+ MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginDeleteObject, (jint)handle);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return result;
}
-MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
- MtpStorageID newStorage, MtpString &newPath) {
+void MtpDatabase::endDeleteObject(MtpObjectHandle handle, bool succeeded) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mDatabase, method_endDeleteObject, (jint)handle, (jboolean) succeeded);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+MtpResponseCode MtpDatabase::beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpStorageID newStorage) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginMoveObject,
+ (jint)handle, (jint)newParent, (jint) newStorage);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return result;
+}
+
+void MtpDatabase::endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent,
+ MtpStorageID oldStorage, MtpStorageID newStorage,
+ MtpObjectHandle handle, bool succeeded) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mDatabase, method_endMoveObject,
+ (jint)oldParent, (jint) newParent, (jint) oldStorage, (jint) newStorage,
+ (jint) handle, (jboolean) succeeded);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+MtpResponseCode MtpDatabase::beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+ MtpStorageID newStorage) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- jstring stringValue = env->NewStringUTF((const char *) newPath);
- MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
- (jint)handle, (jint)newParent, (jint) newStorage, stringValue);
+ MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginCopyObject,
+ (jint)handle, (jint)newParent, (jint) newStorage);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
- env->DeleteLocalRef(stringValue);
return result;
}
+void MtpDatabase::endCopyObject(MtpObjectHandle handle, bool succeeded) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mDatabase, method_endCopyObject, (jint)handle, (jboolean)succeeded);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+
struct PropertyTableEntry {
MtpObjectProperty property;
int type;
@@ -1066,7 +1066,7 @@ static const PropertyTableEntry kDevicePropertyTable[] = {
{ MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, MTP_TYPE_UINT32 },
};
-bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
+bool MtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) {
int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
const PropertyTableEntry* entry = kObjectPropertyTable;
for (int i = 0; i < count; i++, entry++) {
@@ -1078,7 +1078,7 @@ bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type)
return false;
}
-bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
+bool MtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) {
int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]);
const PropertyTableEntry* entry = kDevicePropertyTable;
for (int i = 0; i < count; i++, entry++) {
@@ -1090,7 +1090,7 @@ bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type)
return false;
}
-MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) {
+MtpObjectHandleList* MtpDatabase::getObjectReferences(MtpObjectHandle handle) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectReferences,
(jint)handle);
@@ -1108,7 +1108,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle)
return list;
}
-MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle,
+MtpResponseCode MtpDatabase::setObjectReferences(MtpObjectHandle handle,
MtpObjectHandleList* references) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
int count = references->size();
@@ -1129,7 +1129,7 @@ MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle,
return result;
}
-MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
+MtpProperty* MtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
MtpObjectFormat format) {
static const int channelEnum[] = {
1, // mono
@@ -1210,67 +1210,65 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property,
return result;
}
-MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
+MtpProperty* MtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
MtpProperty* result = NULL;
bool writable = false;
- switch (property) {
- case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
- case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
- writable = true;
- // fall through
- case MTP_DEVICE_PROPERTY_IMAGE_SIZE: {
- result = new MtpProperty(property, MTP_TYPE_STR, writable);
-
- // get current value
- jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty,
- (jint)property, mLongBuffer, mStringBuffer);
- if (ret == MTP_RESPONSE_OK) {
+ // get current value
+ jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty,
+ (jint)property, mLongBuffer, mStringBuffer);
+ if (ret == MTP_RESPONSE_OK) {
+ switch (property) {
+ case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
+ case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
+ writable = true;
+ // fall through
+ case MTP_DEVICE_PROPERTY_IMAGE_SIZE:
+ {
+ result = new MtpProperty(property, MTP_TYPE_STR, writable);
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
result->setCurrentValue(str);
// for read-only properties it is safe to assume current value is default value
if (!writable)
result->setDefaultValue(str);
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
- } else {
- ALOGE("unable to read device property, response: %04X", ret);
+ break;
}
- break;
+ case MTP_DEVICE_PROPERTY_BATTERY_LEVEL:
+ {
+ result = new MtpProperty(property, MTP_TYPE_UINT8);
+ jlong* arr = env->GetLongArrayElements(mLongBuffer, 0);
+ result->setFormRange(0, arr[1], 1);
+ result->mCurrentValue.u.u8 = (uint8_t) arr[0];
+ env->ReleaseLongArrayElements(mLongBuffer, arr, 0);
+ break;
+ }
+ case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
+ {
+ jlong* arr = env->GetLongArrayElements(mLongBuffer, 0);
+ result = new MtpProperty(property, MTP_TYPE_UINT32);
+ result->mCurrentValue.u.u32 = (uint32_t) arr[0];
+ env->ReleaseLongArrayElements(mLongBuffer, arr, 0);
+ break;
+ }
+ default:
+ ALOGE("Unrecognized property %x", property);
}
- case MTP_DEVICE_PROPERTY_BATTERY_LEVEL:
- result = new MtpProperty(property, MTP_TYPE_UINT8);
- result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1);
- result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel);
- break;
- case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE:
- result = new MtpProperty(property, MTP_TYPE_UINT32);
- result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType);
- break;
+ } else {
+ ALOGE("unable to read device property, response: %04X", ret);
}
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return result;
}
-void MyMtpDatabase::sessionStarted() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(mDatabase, method_sessionStarted);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void MyMtpDatabase::sessionEnded() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(mDatabase, method_sessionEnded);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
// ----------------------------------------------------------------------------
static void
android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{
- MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
+ MtpDatabase* database = new MtpDatabase(env, thiz);
env->SetLongField(thiz, field_context, (jlong)database);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
@@ -1278,7 +1276,7 @@ android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
static void
android_mtp_MtpDatabase_finalize(JNIEnv *env, jobject thiz)
{
- MyMtpDatabase* database = (MyMtpDatabase *)env->GetLongField(thiz, field_context);
+ MtpDatabase* database = (MtpDatabase *)env->GetLongField(thiz, field_context);
database->cleanup(env);
delete database;
env->SetLongField(thiz, field_context, 0);
@@ -1305,6 +1303,13 @@ static const JNINativeMethod gMtpPropertyGroupMethods[] = {
(void *)android_mtp_MtpPropertyGroup_format_date_time},
};
+#define GET_METHOD_ID(name, jclass, signature) \
+ method_##name = env->GetMethodID(jclass, #name, signature); \
+ if (method_##name == NULL) { \
+ ALOGE("Can't find " #name); \
+ return -1; \
+ } \
+
int register_android_mtp_MtpDatabase(JNIEnv *env)
{
jclass clazz;
@@ -1314,175 +1319,48 @@ int register_android_mtp_MtpDatabase(JNIEnv *env)
ALOGE("Can't find android/mtp/MtpDatabase");
return -1;
}
- method_beginSendObject = env->GetMethodID(clazz, "beginSendObject", "(Ljava/lang/String;IIIJJ)I");
- if (method_beginSendObject == NULL) {
- ALOGE("Can't find beginSendObject");
- return -1;
- }
- method_endSendObject = env->GetMethodID(clazz, "endSendObject", "(Ljava/lang/String;IIZ)V");
- if (method_endSendObject == NULL) {
- ALOGE("Can't find endSendObject");
- return -1;
- }
- method_doScanDirectory = env->GetMethodID(clazz, "doScanDirectory", "(Ljava/lang/String;)V");
- if (method_doScanDirectory == NULL) {
- ALOGE("Can't find doScanDirectory");
- return -1;
- }
- method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I");
- if (method_getObjectList == NULL) {
- ALOGE("Can't find getObjectList");
- return -1;
- }
- method_getNumObjects = env->GetMethodID(clazz, "getNumObjects", "(III)I");
- if (method_getNumObjects == NULL) {
- ALOGE("Can't find getNumObjects");
- return -1;
- }
- method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I");
- if (method_getSupportedPlaybackFormats == NULL) {
- ALOGE("Can't find getSupportedPlaybackFormats");
- return -1;
- }
- method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I");
- if (method_getSupportedCaptureFormats == NULL) {
- ALOGE("Can't find getSupportedCaptureFormats");
- return -1;
- }
- method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I");
- if (method_getSupportedObjectProperties == NULL) {
- ALOGE("Can't find getSupportedObjectProperties");
- return -1;
- }
- method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I");
- if (method_getSupportedDeviceProperties == NULL) {
- ALOGE("Can't find getSupportedDeviceProperties");
- return -1;
- }
- method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I");
- if (method_setObjectProperty == NULL) {
- ALOGE("Can't find setObjectProperty");
- return -1;
- }
- method_getDeviceProperty = env->GetMethodID(clazz, "getDeviceProperty", "(I[J[C)I");
- if (method_getDeviceProperty == NULL) {
- ALOGE("Can't find getDeviceProperty");
- return -1;
- }
- method_setDeviceProperty = env->GetMethodID(clazz, "setDeviceProperty", "(IJLjava/lang/String;)I");
- if (method_setDeviceProperty == NULL) {
- ALOGE("Can't find setDeviceProperty");
- return -1;
- }
- method_getObjectPropertyList = env->GetMethodID(clazz, "getObjectPropertyList",
- "(IIIII)Landroid/mtp/MtpPropertyList;");
- if (method_getObjectPropertyList == NULL) {
- ALOGE("Can't find getObjectPropertyList");
- return -1;
- }
- method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z");
- if (method_getObjectInfo == NULL) {
- ALOGE("Can't find getObjectInfo");
- return -1;
- }
- method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)I");
- if (method_getObjectFilePath == NULL) {
- ALOGE("Can't find getObjectFilePath");
- return -1;
- }
- method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)I");
- if (method_deleteFile == NULL) {
- ALOGE("Can't find deleteFile");
- return -1;
- }
- method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I");
- if (method_moveObject == NULL) {
- ALOGE("Can't find moveObject");
- return -1;
- }
- method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
- if (method_getObjectReferences == NULL) {
- ALOGE("Can't find getObjectReferences");
- return -1;
- }
- method_setObjectReferences = env->GetMethodID(clazz, "setObjectReferences", "(I[I)I");
- if (method_setObjectReferences == NULL) {
- ALOGE("Can't find setObjectReferences");
- return -1;
- }
- method_sessionStarted = env->GetMethodID(clazz, "sessionStarted", "()V");
- if (method_sessionStarted == NULL) {
- ALOGE("Can't find sessionStarted");
- return -1;
- }
- method_sessionEnded = env->GetMethodID(clazz, "sessionEnded", "()V");
- if (method_sessionEnded == NULL) {
- ALOGE("Can't find sessionEnded");
- return -1;
- }
+ GET_METHOD_ID(beginSendObject, clazz, "(Ljava/lang/String;III)I");
+ GET_METHOD_ID(endSendObject, clazz, "(IZ)V");
+ GET_METHOD_ID(rescanFile, clazz, "(Ljava/lang/String;II)V");
+ GET_METHOD_ID(getObjectList, clazz, "(III)[I");
+ GET_METHOD_ID(getNumObjects, clazz, "(III)I");
+ GET_METHOD_ID(getSupportedPlaybackFormats, clazz, "()[I");
+ GET_METHOD_ID(getSupportedCaptureFormats, clazz, "()[I");
+ GET_METHOD_ID(getSupportedObjectProperties, clazz, "(I)[I");
+ GET_METHOD_ID(getSupportedDeviceProperties, clazz, "()[I");
+ GET_METHOD_ID(setObjectProperty, clazz, "(IIJLjava/lang/String;)I");
+ GET_METHOD_ID(getDeviceProperty, clazz, "(I[J[C)I");
+ GET_METHOD_ID(setDeviceProperty, clazz, "(IJLjava/lang/String;)I");
+ GET_METHOD_ID(getObjectPropertyList, clazz, "(IIIII)Landroid/mtp/MtpPropertyList;");
+ GET_METHOD_ID(getObjectInfo, clazz, "(I[I[C[J)Z");
+ GET_METHOD_ID(getObjectFilePath, clazz, "(I[C[J)I");
+ GET_METHOD_ID(beginDeleteObject, clazz, "(I)I");
+ GET_METHOD_ID(endDeleteObject, clazz, "(IZ)V");
+ GET_METHOD_ID(beginMoveObject, clazz, "(III)I");
+ GET_METHOD_ID(endMoveObject, clazz, "(IIIIIZ)V");
+ GET_METHOD_ID(beginCopyObject, clazz, "(III)I");
+ GET_METHOD_ID(endCopyObject, clazz, "(IZ)V");
+ GET_METHOD_ID(getObjectReferences, clazz, "(I)[I");
+ GET_METHOD_ID(setObjectReferences, clazz, "(I[I)I");
field_context = env->GetFieldID(clazz, "mNativeContext", "J");
if (field_context == NULL) {
ALOGE("Can't find MtpDatabase.mNativeContext");
return -1;
}
- field_batteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I");
- if (field_batteryLevel == NULL) {
- ALOGE("Can't find MtpDatabase.mBatteryLevel");
- return -1;
- }
- field_batteryScale = env->GetFieldID(clazz, "mBatteryScale", "I");
- if (field_batteryScale == NULL) {
- ALOGE("Can't find MtpDatabase.mBatteryScale");
- return -1;
- }
- field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I");
- if (field_deviceType == NULL) {
- ALOGE("Can't find MtpDatabase.mDeviceType");
- return -1;
- }
- // now set up fields for MtpPropertyList class
clazz = env->FindClass("android/mtp/MtpPropertyList");
if (clazz == NULL) {
ALOGE("Can't find android/mtp/MtpPropertyList");
return -1;
}
- field_mCount = env->GetFieldID(clazz, "mCount", "I");
- if (field_mCount == NULL) {
- ALOGE("Can't find MtpPropertyList.mCount");
- return -1;
- }
- field_mResult = env->GetFieldID(clazz, "mResult", "I");
- if (field_mResult == NULL) {
- ALOGE("Can't find MtpPropertyList.mResult");
- return -1;
- }
- field_mObjectHandles = env->GetFieldID(clazz, "mObjectHandles", "[I");
- if (field_mObjectHandles == NULL) {
- ALOGE("Can't find MtpPropertyList.mObjectHandles");
- return -1;
- }
- field_mPropertyCodes = env->GetFieldID(clazz, "mPropertyCodes", "[I");
- if (field_mPropertyCodes == NULL) {
- ALOGE("Can't find MtpPropertyList.mPropertyCodes");
- return -1;
- }
- field_mDataTypes = env->GetFieldID(clazz, "mDataTypes", "[I");
- if (field_mDataTypes == NULL) {
- ALOGE("Can't find MtpPropertyList.mDataTypes");
- return -1;
- }
- field_mLongValues = env->GetFieldID(clazz, "mLongValues", "[J");
- if (field_mLongValues == NULL) {
- ALOGE("Can't find MtpPropertyList.mLongValues");
- return -1;
- }
- field_mStringValues = env->GetFieldID(clazz, "mStringValues", "[Ljava/lang/String;");
- if (field_mStringValues == NULL) {
- ALOGE("Can't find MtpPropertyList.mStringValues");
- return -1;
- }
+ GET_METHOD_ID(getCode, clazz, "()I");
+ GET_METHOD_ID(getCount, clazz, "()I");
+ GET_METHOD_ID(getObjectHandles, clazz, "()[I");
+ GET_METHOD_ID(getPropertyCodes, clazz, "()[I");
+ GET_METHOD_ID(getDataTypes, clazz, "()[I");
+ GET_METHOD_ID(getLongValues, clazz, "()[J");
+ GET_METHOD_ID(getStringValues, clazz, "()[Ljava/lang/String;");
if (AndroidRuntime::registerNativeMethods(env,
"android/mtp/MtpDatabase", gMtpDatabaseMethods, NELEM(gMtpDatabaseMethods)))
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index 6ce104d01a9e..c76cebebc730 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -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");
diff --git a/media/tests/MtpTests/Android.mk b/media/tests/MtpTests/Android.mk
new file mode 100644
index 000000000000..616e600ad6e7
--- /dev/null
+++ b/media/tests/MtpTests/Android.mk
@@ -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)
diff --git a/media/tests/MtpTests/AndroidManifest.xml b/media/tests/MtpTests/AndroidManifest.xml
new file mode 100644
index 000000000000..21e2b0115878
--- /dev/null
+++ b/media/tests/MtpTests/AndroidManifest.xml
@@ -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>
diff --git a/media/tests/MtpTests/AndroidTest.xml b/media/tests/MtpTests/AndroidTest.xml
new file mode 100644
index 000000000000..a61a3b49c8f7
--- /dev/null
+++ b/media/tests/MtpTests/AndroidTest.xml
@@ -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> \ No newline at end of file
diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
new file mode 100644
index 000000000000..0d7f3feaaae6
--- /dev/null
+++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (C) 2017 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.mtp;
+
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.os.storage.StorageVolume;
+import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * Tests for MtpStorageManager functionality.
+ */
+@RunWith(JUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class MtpStorageManagerTest {
+ private static final String TAG = MtpStorageManagerTest.class.getSimpleName();
+
+ private static final String TEMP_DIR = InstrumentationRegistry.getContext().getFilesDir()
+ + "/" + TAG + "/";
+ private static final File TEMP_DIR_FILE = new File(TEMP_DIR);
+
+ private MtpStorageManager manager;
+
+ private ArrayList<Integer> objectsAdded;
+ private ArrayList<Integer> objectsRemoved;
+
+ private File mainStorageDir;
+ private File secondaryStorageDir;
+
+ private MtpStorage mainMtpStorage;
+ private MtpStorage secondaryMtpStorage;
+
+ static {
+ MtpStorageManager.sDebug = true;
+ }
+
+ private static void logMethodName() {
+ Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
+ }
+
+ private static File createNewFile(File parent) {
+ return createNewFile(parent, UUID.randomUUID().toString());
+ }
+
+ private static File createNewFile(File parent, String name) {
+ try {
+ File ret = new File(parent, name);
+ if (!ret.createNewFile())
+ throw new AssertionError("Failed to create file");
+ return ret;
+ } catch (IOException e) {
+ throw new AssertionError(e.getMessage());
+ }
+ }
+
+ private static File createNewDir(File parent, String name) {
+ File ret = new File(parent, name);
+ if (!ret.mkdir())
+ throw new AssertionError("Failed to create file");
+ return ret;
+ }
+
+ private static File createNewDir(File parent) {
+ return createNewDir(parent, UUID.randomUUID().toString());
+ }
+
+ @Before
+ public void before() {
+ Assert.assertTrue(TEMP_DIR_FILE.mkdir());
+ mainStorageDir = createNewDir(TEMP_DIR_FILE);
+ secondaryStorageDir = createNewDir(TEMP_DIR_FILE);
+
+ StorageVolume mainStorage = new StorageVolume("1", mainStorageDir, "", true, false, true,
+ false, -1, UserHandle.CURRENT, "", "");
+ StorageVolume secondaryStorage = new StorageVolume("2", secondaryStorageDir, "", false,
+ false, true, false, -1, UserHandle.CURRENT, "", "");
+
+ objectsAdded = new ArrayList<>();
+ objectsRemoved = new ArrayList<>();
+
+ manager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
+ @Override
+ public void sendObjectAdded(int id) {
+ objectsAdded.add(id);
+ }
+
+ @Override
+ public void sendObjectRemoved(int id) {
+ objectsRemoved.add(id);
+ }
+ }, null);
+
+ mainMtpStorage = manager.addMtpStorage(mainStorage);
+ secondaryMtpStorage = manager.addMtpStorage(secondaryStorage);
+ }
+
+ @After
+ public void after() {
+ manager.close();
+ FileUtils.deleteContentsAndDir(TEMP_DIR_FILE);
+ }
+
+ /** MtpObject getter tests. **/
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetNameRoot() {
+ logMethodName();
+ MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+ Assert.assertEquals(obj.getName(), mainStorageDir.getPath());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetNameNonRoot() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getName(), newFile.getName());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetIdRoot() {
+ logMethodName();
+ MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+ Assert.assertEquals(obj.getId(), mainMtpStorage.getStorageId());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetIdNonRoot() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getId(), 1);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectIsDirTrue() {
+ logMethodName();
+ MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+ Assert.assertTrue(obj.isDir());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectIsDirFalse() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir, "TEST123.mp3");
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertFalse(stream.findFirst().get().isDir());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetFormatDir() {
+ logMethodName();
+ File newFile = createNewDir(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetFormatNonDir() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir, "TEST123.mp3");
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_MP3);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetStorageId() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getStorageId(), mainMtpStorage.getStorageId());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetLastModified() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getModifiedTime(),
+ newFile.lastModified() / 1000);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetParent() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getParent(),
+ manager.getStorageRoot(mainMtpStorage.getStorageId()));
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetRoot() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getRoot(),
+ manager.getStorageRoot(mainMtpStorage.getStorageId()));
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetPath() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getPath().toString(), newFile.getPath());
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetSize() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ try {
+ new FileOutputStream(newFile).write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0});
+ } catch (IOException e) {
+ Assert.fail();
+ }
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getSize(), 8);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpObjectGetSizeDir() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getSize(), 0);
+ }
+
+ /** MtpStorageManager cache access tests. **/
+
+ @Test
+ @SmallTest
+ public void testAddMtpStorage() {
+ logMethodName();
+ Assert.assertEquals(mainMtpStorage.getPath(), mainStorageDir.getPath());
+ Assert.assertNotNull(manager.getStorageRoot(mainMtpStorage.getStorageId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveMtpStorage() {
+ logMethodName();
+ File newFile = createNewFile(secondaryStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ secondaryMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 1);
+
+ manager.removeMtpStorage(secondaryMtpStorage);
+ Assert.assertNull(manager.getStorageRoot(secondaryMtpStorage.getStorageId()));
+ Assert.assertNull(manager.getObject(1));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetByPath() {
+ logMethodName();
+ File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+ MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath());
+ Assert.assertNotNull(obj);
+ Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetByPathError() {
+ logMethodName();
+ File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+ MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath() + "q");
+ Assert.assertNull(obj);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObject() {
+ logMethodName();
+ File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+ MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath());
+ Assert.assertNotNull(obj);
+
+ Assert.assertEquals(manager.getObject(obj.getId()), obj);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectError() {
+ logMethodName();
+ File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir)));
+
+ Assert.assertNull(manager.getObject(42));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetStorageRoot() {
+ logMethodName();
+ MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId());
+ Assert.assertEquals(obj.getPath().toString(), mainStorageDir.getPath());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsParent() {
+ logMethodName();
+ File newDir = createNewDir(createNewDir(mainStorageDir));
+ File newFile = createNewFile(newDir);
+ File newMP3File = createNewFile(newDir, "lalala.mp3");
+ MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath());
+ Assert.assertNotNull(parent);
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(), 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 2);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsFormat() {
+ logMethodName();
+ File newDir = createNewDir(createNewDir(mainStorageDir));
+ File newFile = createNewFile(newDir);
+ File newMP3File = createNewFile(newDir, "lalala.mp3");
+ MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath());
+ Assert.assertNotNull(parent);
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(),
+ MtpConstants.FORMAT_MP3, mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.findFirst().get().getPath().toString(), newMP3File.toString());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsRoot() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ File newFile = createNewFile(mainStorageDir);
+ File newMP3File = createNewFile(newDir, "lalala.mp3");
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 2);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsAll() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ File newFile = createNewFile(mainStorageDir);
+ File newMP3File = createNewFile(newDir, "lalala.mp3");
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 3);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsAllStorages() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ createNewFile(mainStorageDir);
+ createNewFile(newDir, "lalala.mp3");
+ File newDir2 = createNewDir(secondaryStorageDir);
+ createNewFile(secondaryStorageDir);
+ createNewFile(newDir2, "lalala.mp3");
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0, 0xFFFFFFFF);
+ Assert.assertEquals(stream.count(), 6);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetObjectsAllStoragesRoot() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ createNewFile(mainStorageDir);
+ createNewFile(newDir, "lalala.mp3");
+ File newDir2 = createNewDir(secondaryStorageDir);
+ createNewFile(secondaryStorageDir);
+ createNewFile(newDir2, "lalala.mp3");
+
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, 0xFFFFFFFF);
+ Assert.assertEquals(stream.count(), 4);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ /** MtpStorageManager event handling tests. **/
+
+ @Test
+ @SmallTest
+ public void testObjectAdded() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 0);
+
+ File newFile = createNewFile(mainStorageDir);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ newFile.getPath());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testObjectAddedDir() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 0);
+
+ File newDir = createNewDir(mainStorageDir);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ newDir.getPath());
+ Assert.assertTrue(manager.getObject(objectsAdded.get(0)).isDir());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testObjectAddedRecursiveDir() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 0);
+
+ File newDir = createNewDir(createNewDir(createNewDir(mainStorageDir)));
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 3);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(2)).getPath().toString(),
+ newDir.getPath());
+ Assert.assertTrue(manager.getObject(objectsAdded.get(2)).isDir());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testObjectRemoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 1);
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertEquals(objectsRemoved.size(), 1);
+ Assert.assertNull(manager.getObject(objectsRemoved.get(0)));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testObjectMoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ Assert.assertEquals(stream.count(), 1);
+ File toFile = new File(mainStorageDir, "to" + newFile.getName());
+
+ Assert.assertTrue(newFile.renameTo(toFile));
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ toFile.getPath());
+ Assert.assertNull(manager.getObject(objectsRemoved.get(0)));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ /** MtpStorageManager operation tests. Ensure that events are not sent for the main operation,
+ and also test all possible cases of other processes accessing the file at the same time, as
+ well as cases of both failure and success. **/
+
+ @Test
+ @SmallTest
+ public void testSendObjectSuccess() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+
+ File newFile = createNewFile(mainStorageDir, "newFile");
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, true));
+ Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectSuccessDir() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newDir", MtpConstants.FORMAT_ASSOCIATION);
+ Assert.assertEquals(id, 1);
+
+ File newFile = createNewDir(mainStorageDir, "newDir");
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, true));
+ Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Check that new dir receives events
+ File newerFile = createNewFile(newFile);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ newerFile.getPath());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectSuccessDelayed() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, true));
+
+ File newFile = createNewFile(mainStorageDir, "newFile");
+ manager.flushEvents();
+ Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectSuccessDirDelayed() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newDir", MtpConstants.FORMAT_ASSOCIATION);
+ Assert.assertEquals(id, 1);
+
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, true));
+ File newFile = createNewDir(mainStorageDir, "newDir");
+ manager.flushEvents();
+ Assert.assertEquals(obj.getPath().toString(), newFile.getPath());
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION);
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Check that new dir receives events
+ File newerFile = createNewFile(newFile);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ newerFile.getPath());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectSuccessDeleted() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+
+ File newFile = createNewFile(mainStorageDir, "newFile");
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, true));
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId());
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectFailed() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endSendObject(obj, false));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectFailedDeleted() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+
+ File newFile = createNewFile(mainStorageDir, "newFile");
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endSendObject(obj, false));
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testSendObjectFailedAdded() {
+ logMethodName();
+ Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId());
+ int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ "newFile", MtpConstants.FORMAT_UNDEFINED);
+ Assert.assertEquals(id, 1);
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+
+ File newDir = createNewDir(mainStorageDir, "newFile");
+ manager.flushEvents();
+ Assert.assertTrue(manager.endSendObject(obj, false));
+ Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+ Assert.assertNull(manager.getObject(id));
+ Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(),
+ newDir.getPath());
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events in new dir
+ createNewFile(newDir);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 2);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectSuccess() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRemoveObject(obj, true));
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectDelayed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(manager.endRemoveObject(obj, true));
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectDir() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ createNewFile(createNewDir(newDir));
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ createNewFile(newDir);
+ Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRemoveObject(obj, true));
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+ Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0);
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectDirDelayed() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ createNewFile(createNewDir(newDir));
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(manager.endRemoveObject(obj, true));
+ Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir));
+ manager.flushEvents();
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0);
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectSuccessAdded() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ int id = obj.getId();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(newFile.delete());
+ createNewFile(mainStorageDir, newFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRemoveObject(obj, true));
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertNull(manager.getObject(id));
+ Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectFailed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(manager.endRemoveObject(obj, false));
+ Assert.assertEquals(manager.getObject(obj.getId()), obj);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectFailedDir() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ createNewFile(newDir);
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRemoveObject(obj, false));
+ Assert.assertEquals(manager.getObject(obj.getId()), obj);
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRemoveObjectFailedRemoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRemoveObject(obj));
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRemoveObject(obj, false));
+ Assert.assertEquals(objectsRemoved.size(), 1);
+ Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId());
+ Assert.assertNull(manager.getObject(obj.getId()));
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testCopyObjectSuccess() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+
+ int id = manager.beginCopyObject(fileObj, dirObj);
+ Assert.assertNotEquals(id, -1);
+ createNewFile(newDir, newFile.getName());
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endCopyObject(obj, true));
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testCopyObjectSuccessRecursive() {
+ logMethodName();
+ File newDirFrom = createNewDir(mainStorageDir);
+ File newDirFrom1 = createNewDir(newDirFrom);
+ File newDirFrom2 = createNewFile(newDirFrom1);
+ File delayedFile = createNewFile(newDirFrom);
+ File deletedFile = createNewFile(newDirFrom);
+ File newDirTo = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject toObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(newDirTo.getName())).findFirst().get();
+ MtpStorageManager.MtpObject fromObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(newDirFrom.getName())).findFirst().get();
+
+ manager.getObjects(fromObj.getId(), 0, mainMtpStorage.getStorageId());
+ int id = manager.beginCopyObject(fromObj, toObj);
+ Assert.assertNotEquals(id, -1);
+ File copiedDir = createNewDir(newDirTo, newDirFrom.getName());
+ File copiedDir1 = createNewDir(copiedDir, newDirFrom1.getName());
+ createNewFile(copiedDir1, newDirFrom2.getName());
+ createNewFile(copiedDir, "extraFile");
+ File toDelete = createNewFile(copiedDir, deletedFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(toDelete.delete());
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endCopyObject(obj, true));
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+
+ createNewFile(copiedDir, delayedFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events in the visited dir, but not the unvisited dir.
+ createNewFile(copiedDir);
+ createNewFile(copiedDir1);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 2);
+ Assert.assertEquals(objectsAdded.size(), 2);
+
+ // Number of files/dirs created, minus the one that was deleted.
+ Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 13);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testCopyObjectFailed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+
+ int id = manager.beginCopyObject(fileObj, dirObj);
+ Assert.assertNotEquals(id, -1);
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endCopyObject(obj, false));
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testCopyObjectFailedAdded() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+
+ int id = manager.beginCopyObject(fileObj, dirObj);
+ Assert.assertNotEquals(id, -1);
+ File addedDir = createNewDir(newDir, newFile.getName());
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endCopyObject(obj, false));
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertNotEquals(objectsAdded.get(0).intValue(), id);
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events in new dir
+ createNewFile(addedDir);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 2);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testCopyObjectFailedDeleted() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+
+ int id = manager.beginCopyObject(fileObj, dirObj);
+ Assert.assertNotEquals(id, -1);
+ Assert.assertTrue(createNewFile(newDir, newFile.getName()).delete());
+ manager.flushEvents();
+ MtpStorageManager.MtpObject obj = manager.getObject(id);
+ Assert.assertTrue(manager.endCopyObject(obj, false));
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectSuccess() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ File renamed = new File(mainStorageDir, "renamed");
+ Assert.assertTrue(newFile.renameTo(renamed));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectDirSuccess() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ File renamed = new File(mainStorageDir, "renamed");
+ Assert.assertTrue(newDir.renameTo(renamed));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Don't expect events
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectDirVisitedSuccess() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ File renamed = new File(mainStorageDir, "renamed");
+ Assert.assertTrue(newDir.renameTo(renamed));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectDelayed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true));
+ File renamed = new File(mainStorageDir, "renamed");
+ Assert.assertTrue(newFile.renameTo(renamed));
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectDirVisitedDelayed() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true));
+ File renamed = new File(mainStorageDir, "renamed");
+ Assert.assertTrue(newDir.renameTo(renamed));
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(obj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectFailed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectFailedOldRemoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testRenameObjectFailedNewAdded() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginRenameObject(obj, "renamed"));
+
+ createNewFile(mainStorageDir, "renamed");
+ manager.flushEvents();
+ Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectSuccess() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File dir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+ File moved = new File(dir, newFile.getName());
+ Assert.assertTrue(newFile.renameTo(moved));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, newFile.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(fileObj.getPath().toString(), moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectDirSuccess() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+ File renamed = new File(newDir, movedDir.getName());
+ Assert.assertTrue(movedDir.renameTo(renamed));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, movedDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Don't expect events
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectDirVisitedSuccess() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+ manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+ File renamed = new File(newDir, movedDir.getName());
+ Assert.assertTrue(movedDir.renameTo(renamed));
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, movedDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectDelayed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File dir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, newFile.getName(), true));
+
+ File moved = new File(dir, newFile.getName());
+ Assert.assertTrue(newFile.renameTo(moved));
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(fileObj.getPath().toString(), moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectDirVisitedDelayed() {
+ logMethodName();
+ File newDir = createNewDir(mainStorageDir);
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(newDir.getName())).findFirst().get();
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get();
+ manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, movedDir.getName(), true));
+
+ File renamed = new File(newDir, movedDir.getName());
+ Assert.assertTrue(movedDir.renameTo(renamed));
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(renamed);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectFailed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File dir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectFailedOldRemoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File dir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectFailedNewAdded() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ File dir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(MtpStorageManager.MtpObject::isDir).findFirst().get();
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId())
+ .filter(o -> !o.isDir()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj));
+
+ createNewFile(dir, newFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ dirObj, newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageSuccess() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(newFile.delete());
+ File moved = createNewFile(secondaryStorageDir, newFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ newFile.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObject(fileObj.getId()).getPath().toString(),
+ moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageDirSuccess() {
+ logMethodName();
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(movedObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(movedDir.delete());
+ File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ movedDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+ moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Don't expect events
+ createNewFile(moved);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageDirVisitedSuccess() {
+ logMethodName();
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginMoveObject(movedObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(movedDir.delete());
+ File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ movedDir.getName(), true));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+ moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(moved);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageDelayed() {
+ logMethodName();
+ File movedFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(movedObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ movedFile.getName(), true));
+
+ Assert.assertTrue(movedFile.delete());
+ File moved = createNewFile(secondaryStorageDir, movedFile.getName());
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+ moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageDirVisitedDelayed() {
+ logMethodName();
+ File movedDir = createNewDir(mainStorageDir);
+ MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId());
+ Assert.assertTrue(manager.beginMoveObject(movedObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ movedDir.getName(), true));
+
+ Assert.assertTrue(movedDir.delete());
+ File moved = createNewDir(secondaryStorageDir, movedDir.getName());
+ manager.flushEvents();
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+ Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(),
+ moved.getPath());
+
+ Assert.assertTrue(manager.checkConsistency());
+
+ // Expect events since the dir was visited
+ createNewFile(moved);
+ manager.flushEvents();
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageFailed() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageFailedOldRemoved() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ Assert.assertTrue(newFile.delete());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 0);
+ Assert.assertEquals(objectsRemoved.size(), 1);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+
+ @Test
+ @SmallTest
+ public void testMoveObjectXStorageFailedNewAdded() {
+ logMethodName();
+ File newFile = createNewFile(mainStorageDir);
+ MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0,
+ mainMtpStorage.getStorageId()).findFirst().get();
+ Assert.assertTrue(manager.beginMoveObject(fileObj,
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId())));
+
+ createNewFile(secondaryStorageDir, newFile.getName());
+ manager.flushEvents();
+ Assert.assertTrue(manager.endMoveObject(
+ manager.getStorageRoot(mainMtpStorage.getStorageId()),
+ manager.getStorageRoot(secondaryMtpStorage.getStorageId()),
+ newFile.getName(), false));
+
+ Assert.assertEquals(objectsAdded.size(), 1);
+ Assert.assertEquals(objectsRemoved.size(), 0);
+
+ Assert.assertTrue(manager.checkConsistency());
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 7f0b50817d56..ac800e727462 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2691,15 +2691,14 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon
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));
}