Adjust MTP to reference by specific volume name.

The MediaStore.VOLUME_EXTERNAL volume is a merged view of all storage
devices, and clients working on a specific volume need to focus on
the volume they're interested in.

Bug: 129840030
Test: atest --test-mapping packages/providers/MediaProvider
Change-Id: I91cee6a96d7f9360e6a93a9a3c389b097b6b9967
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 6361fd8..4ac6d35 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -36,6 +36,7 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.WindowManager;
 
@@ -69,8 +70,6 @@
 
     private final Context mContext;
     private final ContentProviderClient mMediaProvider;
-    private final String mVolumeName;
-    private final Uri mObjectsUri;
 
     private final AtomicBoolean mClosed = new AtomicBoolean();
     private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -78,10 +77,10 @@
     private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>();
 
     // cached property groups for single properties
-    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>();
+    private final SparseArray<MtpPropertyGroup> mPropertyGroupsByProperty = new SparseArray<>();
 
     // cached property groups for all properties for a given format
-    private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>();
+    private final SparseArray<MtpPropertyGroup> mPropertyGroupsByFormat = new SparseArray<>();
 
     // SharedPreferences for writable MTP device properties
     private SharedPreferences mDeviceProperties;
@@ -271,14 +270,11 @@
         }
     };
 
-    public MtpDatabase(Context context, String volumeName,
-            String[] subDirectories) {
+    public MtpDatabase(Context context, String[] subDirectories) {
         native_setup();
         mContext = Objects.requireNonNull(context);
         mMediaProvider = context.getContentResolver()
                 .acquireContentProviderClient(MediaStore.AUTHORITY);
-        mVolumeName = volumeName;
-        mObjectsUri = Files.getMtpObjectsUri(volumeName);
         mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
             @Override
             public void sendObjectAdded(int id) {
@@ -526,8 +522,7 @@
                 propertyGroup = mPropertyGroupsByFormat.get(format);
                 if (propertyGroup == null) {
                     final int[] propertyList = getSupportedObjectProperties(format);
-                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
-                            propertyList);
+                    propertyGroup = new MtpPropertyGroup(propertyList);
                     mPropertyGroupsByFormat.put(format, propertyGroup);
                 }
             } else {
@@ -535,12 +530,11 @@
                 propertyGroup = mPropertyGroupsByProperty.get(property);
                 if (propertyGroup == null) {
                     final int[] propertyList = new int[]{property};
-                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
-                            propertyList);
+                    propertyGroup = new MtpPropertyGroup(propertyList);
                     mPropertyGroupsByProperty.put(property, propertyGroup);
                 }
             }
-            int err = propertyGroup.getPropertyList(obj, ret);
+            int err = propertyGroup.getPropertyList(mMediaProvider, obj.getVolumeName(), obj, ret);
             if (err != MtpConstants.RESPONSE_OK) {
                 return new MtpPropertyList(err);
             }
@@ -581,7 +575,8 @@
         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.
-            mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
+            final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+            mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
         }
@@ -640,12 +635,12 @@
         if (obj.getParent().isRoot()) {
             values.put(Files.FileColumns.PARENT, 0);
         } else {
-            int parentId = findInMedia(path.getParent());
+            int parentId = findInMedia(newParentObj, 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());
+                deleteFromMedia(obj, oldPath, obj.isDir());
                 return;
             }
         }
@@ -655,13 +650,14 @@
         try {
             int parentId = -1;
             if (!oldParentObj.isRoot()) {
-                parentId = findInMedia(oldPath.getParent());
+                parentId = findInMedia(oldParentObj, 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);
+                final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+                mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
             } else {
                 // Old parent doesn't exist - add the object
                 MediaStore.scanFile(mContext, path.toFile());
@@ -823,14 +819,16 @@
         if (!mManager.endRemoveObject(obj, success))
             Log.e(TAG, "Failed to end remove object");
         if (success)
-            deleteFromMedia(obj.getPath(), obj.isDir());
+            deleteFromMedia(obj, obj.getPath(), obj.isDir());
     }
 
-    private int findInMedia(Path path) {
+    private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
+        final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
+
         int ret = -1;
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
+            c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
                     new String[]{path.toString()}, null, null);
             if (c != null && c.moveToNext()) {
                 ret = c.getInt(0);
@@ -844,12 +842,13 @@
         return ret;
     }
 
-    private void deleteFromMedia(Path path, boolean isDir) {
+    private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
+        final Uri objectsUri = MediaStore.Files.getMtpObjectsUri(obj.getVolumeName());
         try {
             // Delete the object(s) from MediaProvider, but ignore errors.
             if (isDir) {
                 // recursive case - delete all children first
-                mMediaProvider.delete(mObjectsUri,
+                mMediaProvider.delete(objectsUri,
                         // 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)",
@@ -858,7 +857,7 @@
             }
 
             String[] whereArgs = new String[]{path.toString()};
-            if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
+            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
                 if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
                     MediaStore.scanFile(mContext, path.getParent().toFile());
                 }
@@ -876,10 +875,10 @@
         if (obj == null)
             return null;
         // Translate this handle to the MediaProvider Handle
-        handle = findInMedia(obj.getPath());
+        handle = findInMedia(obj, obj.getPath());
         if (handle == -1)
             return null;
-        Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
+        Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
         Cursor c = null;
         try {
             c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null);
@@ -912,17 +911,17 @@
         if (obj == null)
             return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
         // Translate this handle to the MediaProvider Handle
-        handle = findInMedia(obj.getPath());
+        handle = findInMedia(obj, obj.getPath());
         if (handle == -1)
             return MtpConstants.RESPONSE_GENERAL_ERROR;
-        Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
+        Uri uri = Files.getMtpReferencesUri(obj.getVolumeName(), handle);
         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());
+            int refHandle = findInMedia(refObj, refObj.getPath());
             if (refHandle == -1)
                 continue;
             ContentValues values = new ContentValues();
diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java
index 6d5be8e..5bb0c1b 100644
--- a/media/java/android/mtp/MtpPropertyGroup.java
+++ b/media/java/android/mtp/MtpPropertyGroup.java
@@ -46,9 +46,6 @@
         }
     }
 
-    private final ContentProviderClient mProvider;
-    private final String mVolumeName;
-
     // list of all properties in this group
     private final Property[] mProperties;
 
@@ -58,10 +55,7 @@
     private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";
 
     // constructs a property group for a list of properties
-    public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
-        mProvider = provider;
-        mVolumeName = volumeName;
-
+    public MtpPropertyGroup(int[] properties) {
         int count = properties.length;
         ArrayList<String> columns = new ArrayList<>(count);
         columns.add(Files.FileColumns._ID);
@@ -175,7 +169,8 @@
      * object and adds them to the given property list.
      * @return Response_OK if the operation succeeded.
      */
-    public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) {
+    public int getPropertyList(ContentProviderClient provider, String volumeName,
+            MtpStorageManager.MtpObject object, MtpPropertyList list) {
         Cursor c = null;
         int id = object.getId();
         String path = object.getPath().toString();
@@ -184,8 +179,8 @@
                 try {
                     // Look up the entry in MediaProvider only if one of those properties is needed.
                     final Uri uri = MtpDatabase.getObjectPropertiesUri(object.getFormat(),
-                            mVolumeName);
-                    c = mProvider.query(uri, mColumns,
+                            volumeName);
+                    c = provider.query(uri, mColumns,
                             PATH_WHERE, new String[] {path}, null, null);
                     if (c != null && !c.moveToNext()) {
                         c.close();
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index c714b3c..65d0fef 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -18,6 +18,7 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.os.storage.StorageVolume;
+import android.provider.MediaStore;
 
 /**
  * This class represents a storage unit on an MTP device.
@@ -27,12 +28,12 @@
  * @hide
  */
 public class MtpStorage {
-
     private final int mStorageId;
     private final String mPath;
     private final String mDescription;
     private final boolean mRemovable;
     private final long mMaxFileSize;
+    private final String mVolumeName;
 
     public MtpStorage(StorageVolume volume, int storageId) {
         mStorageId = storageId;
@@ -40,6 +41,11 @@
         mDescription = volume.getDescription(null);
         mRemovable = volume.isRemovable();
         mMaxFileSize = volume.getMaxFileSize();
+        if (volume.isPrimary()) {
+            mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
+        } else {
+            mVolumeName = volume.getNormalizedUuid();
+        }
     }
 
     /**
@@ -88,4 +94,8 @@
     public long getMaxFileSize() {
         return mMaxFileSize;
     }
+
+    public String getVolumeName() {
+        return mVolumeName;
+    }
 }
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
index f14e7d7..e783788 100644
--- a/media/java/android/mtp/MtpStorageManager.java
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -21,6 +21,8 @@
 import android.os.storage.StorageVolume;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.io.IOException;
 import java.nio.file.DirectoryIteratorException;
 import java.nio.file.DirectoryStream;
@@ -131,6 +133,7 @@
 
     /** MtpObject represents either a file or directory in an associated storage. **/
     public static class MtpObject {
+        private MtpStorage mStorage;
         // null for root objects
         private MtpObject mParent;
 
@@ -147,9 +150,10 @@
         // null if not both a directory and visited
         private FileObserver mObserver;
 
-        MtpObject(String name, int id, MtpObject parent, boolean isDir) {
+        MtpObject(String name, int id, MtpStorage storage, MtpObject parent, boolean isDir) {
             mId = id;
             mName = name;
+            mStorage = Preconditions.checkNotNull(storage);
             mParent = parent;
             mObserver = null;
             mVisited = false;
@@ -206,6 +210,10 @@
             return mParent == null;
         }
 
+        public String getVolumeName() {
+            return mStorage.getVolumeName();
+        }
+
         /** For MtpStorageManager only **/
 
         private void setName(String name) {
@@ -278,7 +286,7 @@
         }
 
         private MtpObject copy(boolean recursive) {
-            MtpObject copy = new MtpObject(mName, mId, mParent, mIsDir);
+            MtpObject copy = new MtpObject(mName, mId, mStorage, mParent, mIsDir);
             copy.mIsDir = mIsDir;
             copy.mVisited = mVisited;
             copy.mState = mState;
@@ -408,7 +416,7 @@
     public synchronized MtpStorage addMtpStorage(StorageVolume volume) {
         int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1;
         MtpStorage storage = new MtpStorage(volume, storageId);
-        MtpObject root = new MtpObject(storage.getPath(), storageId, null, true);
+        MtpObject root = new MtpObject(storage.getPath(), storageId, storage, null, true);
         mRoots.put(storageId, root);
         return storage;
     }
@@ -608,7 +616,7 @@
             return null;
         }
 
-        MtpObject obj = new MtpObject(newName, getNextObjectId(), parent, isDir);
+        MtpObject obj = new MtpObject(newName, getNextObjectId(), parent.mStorage, parent, isDir);
         mObjects.put(obj.getId(), obj);
         parent.addChild(obj);
         return obj;