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

Commit 20079f10 authored by Ivan Chiang's avatar Ivan Chiang
Browse files

Fix renaming file and moving file issue in MTP

After the user renames/moves a file/directory, the database of
MediaProvider is not updated. It causes that the file can't be
opened via MediaProvider. Use scanFile to instead of MP.update
for the file/directory.

Handle the .nomedia case for create/copy/move/delete/rename.
Scan the parent directory to update the database.

Test: atest MtpTests
Test: atest --test-mapping packages/providers/MediaProvider
Test: manual. 1. Move/rename the file/directory.
              2. Open the file in DocumentsUI
	      3. create/copy/move/delete/rename for .nomedia case
Bug: 169300366
Bug: 171538933
Bug: 172029805
Bug: 170798581
Change-Id: Ia37be7a027bfe502e3f7c96babe5a5bbed5f942c
parent 29f0617c
Loading
Loading
Loading
Loading
+24 −96
Original line number Diff line number Diff line
@@ -19,8 +19,7 @@ package android.mtp;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentProviderClient;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,7 +31,6 @@ import android.media.ExifInterface;
import android.media.ThumbnailUtils;
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;
@@ -103,8 +101,6 @@ public class MtpDatabase implements AutoCloseable {
    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 {
@@ -431,7 +427,7 @@ public class MtpDatabase implements AutoCloseable {
        }
        // Add the new file to MediaProvider
        if (succeeded) {
            MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());
            updateMediaStore(mContext, obj.getPath().toFile());
        }
    }

@@ -580,32 +576,8 @@ public class MtpDatabase implements AutoCloseable {
            return MtpConstants.RESPONSE_GENERAL_ERROR;
        }

        // finally update MediaProvider
        ContentValues values = new ContentValues();
        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.
            final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
            mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
        }

        // check if nomedia status changed
        if (obj.isDir()) {
            // for directories, check if renamed from something hidden to something non-hidden
            if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
                MediaStore.scanFile(mContext.getContentResolver(), newPath.toFile());
            }
        } else {
            // for files, check if renamed from .nomedia to something else
            if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
                    && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
                MediaStore.scanFile(mContext.getContentResolver(), newPath.getParent().toFile());
            }
        }
        updateMediaStore(mContext, oldPath.toFile());
        updateMediaStore(mContext, newPath.toFile());
        return MtpConstants.RESPONSE_OK;
    }

@@ -635,48 +607,15 @@ public class MtpDatabase implements AutoCloseable {
            Log.e(TAG, "Failed to end move object");
            return;
        }

        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();

        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(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(obj, oldPath, obj.isDir());
                return;
            }
        }
        // update MediaProvider
        Cursor c = null;
        String[] whereArgs = new String[]{oldPath.toString()};
        try {
            int parentId = -1;
            if (!oldParentObj.isRoot()) {
                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.
                final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
                mMediaProvider.update(objectsUri, values, PATH_WHERE, whereArgs);
            } else {
                // Old parent doesn't exist - add the object
                MediaStore.scanFile(mContext.getContentResolver(), path.toFile());
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
        }

        updateMediaStore(mContext, oldPath.toFile());
        updateMediaStore(mContext, path.toFile());
    }

    @VisibleForNative
@@ -699,7 +638,19 @@ public class MtpDatabase implements AutoCloseable {
        if (!success) {
            return;
        }
        MediaStore.scanFile(mContext.getContentResolver(), obj.getPath().toFile());

        updateMediaStore(mContext, obj.getPath().toFile());
    }

    private static void updateMediaStore(@NonNull Context context, @NonNull File file) {
        final ContentResolver resolver = context.getContentResolver();
        // For file, check whether the file name is .nomedia or not.
        // If yes, scan the parent directory to update all files in the directory.
        if (!file.isDirectory() && file.getName().toLowerCase(Locale.ROOT).endsWith(NO_MEDIA)) {
            MediaStore.scanFile(resolver, file.getParentFile());
        } else {
            MediaStore.scanFile(resolver, file);
        }
    }

    @VisibleForNative
@@ -928,26 +879,6 @@ public class MtpDatabase implements AutoCloseable {
            deleteFromMedia(obj, obj.getPath(), obj.isDir());
    }

    private int findInMedia(MtpStorageManager.MtpObject obj, Path path) {
        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());

        int ret = -1;
        Cursor c = null;
        try {
            c = mMediaProvider.query(objectsUri, ID_PROJECTION, PATH_WHERE,
                    new String[]{path.toString()}, null, null);
            if (c != null && c.moveToNext()) {
                ret = c.getInt(0);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Error finding " + path + " in MediaProvider");
        } finally {
            if (c != null)
                c.close();
        }
        return ret;
    }

    private void deleteFromMedia(MtpStorageManager.MtpObject obj, Path path, boolean isDir) {
        final Uri objectsUri = MediaStore.Files.getContentUri(obj.getVolumeName());
        try {
@@ -963,13 +894,10 @@ public class MtpDatabase implements AutoCloseable {
            }

            String[] whereArgs = new String[]{path.toString()};
            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) > 0) {
                if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
                    MediaStore.scanFile(mContext.getContentResolver(), path.getParent().toFile());
                }
            } else {
                Log.i(TAG, "Mediaprovider didn't delete " + path);
            if (mMediaProvider.delete(objectsUri, PATH_WHERE, whereArgs) == 0) {
                Log.i(TAG, "MediaProvider didn't delete " + path);
            }
            updateMediaStore(mContext, path.toFile());
        } catch (Exception e) {
            Log.d(TAG, "Failed to delete " + path + " from MediaProvider");
        }