Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42bf6d8a authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

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
parent 469f1c90
Loading
Loading
Loading
Loading
+28 −29
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.system.ErrnoException;
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 @@ public class MtpDatabase implements AutoCloseable {

    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 @@ public class MtpDatabase implements AutoCloseable {
    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 class MtpDatabase implements AutoCloseable {
        }
    };

    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 @@ public class MtpDatabase implements AutoCloseable {
                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 @@ public class MtpDatabase implements AutoCloseable {
                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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
            }

            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 @@ public class MtpDatabase implements AutoCloseable {
        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 @@ public class MtpDatabase implements AutoCloseable {
        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();
+5 −10
Original line number Diff line number Diff line
@@ -46,9 +46,6 @@ class MtpPropertyGroup {
        }
    }

    private final ContentProviderClient mProvider;
    private final String mVolumeName;

    // list of all properties in this group
    private final Property[] mProperties;

@@ -58,10 +55,7 @@ class MtpPropertyGroup {
    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 @@ class MtpPropertyGroup {
     * 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 @@ class MtpPropertyGroup {
                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();
+11 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.mtp;

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 @@ import android.os.storage.StorageVolume;
 * @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 @@ public class MtpStorage {
        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 class MtpStorage {
    public long getMaxFileSize() {
        return mMaxFileSize;
    }

    public String getVolumeName() {
        return mVolumeName;
    }
}
+12 −4
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.os.FileObserver;
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 @@ public class MtpStorageManager {

    /** 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 @@ public class MtpStorageManager {
        // 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 @@ public class MtpStorageManager {
            return mParent == null;
        }

        public String getVolumeName() {
            return mStorage.getVolumeName();
        }

        /** For MtpStorageManager only **/

        private void setName(String name) {
@@ -278,7 +286,7 @@ public class MtpStorageManager {
        }

        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 class MtpStorageManager {
    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 @@ public class MtpStorageManager {
            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;