summaryrefslogtreecommitdiff
path: root/media/java/android/mtp/MtpDatabase.java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java/android/mtp/MtpDatabase.java')
-rwxr-xr-xmedia/java/android/mtp/MtpDatabase.java1339
1 files changed, 570 insertions, 769 deletions
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;