artman
artman

Reputation: 641

Android 10: How to delete MediaStore item and it's associated data on file system programmatically?

I am updating my app to use Scoped Storage feature introduced in Android 10.

My app works with MediaStore and displays images, videos and audio files and provides ability for user to delete item.

What I did earlier to delete file:

  1. Got path from MediaStore.MediaColumns.DATA
  2. Used new File(path).delete() to delete that file
  3. Manually updating MediaStore

Now that MediaStore.MediaColumns.DATA is not available I migrated to deleting items from MediaStore using ContentResolver.delete()

For example I have uri of the item: content://media/external/images/media/502 (its valid uri, I display it's thumbnail in grid). It doesnt matter whether I inserted this item in MediaStore or some other app did.

I use context.getContentResolver().delete(uri, null, null). It either succeeds in deletion (returns 1 row) or catching RecoverableSecurityException to use startIntentSenderForResult() to get access to current uri and then using the same getContentResolver().delete() to delete it in onActivityResult() and then getting successful deletion.

Either way that item is removed from MediaStore and is neither showing in result when I query MediaStore for images, nor in other applications.

BUT this file exists on file system (checked using SAF and various file managers (Google Files, Total Commander))

Sometimes (depends on Android version and media type) these items are brought back to MediaStore after phone reboot (or after opening Google Photos - it scans file system)

For example: Android 10 on my Google Pixel and Google Pixel 3a XL behaves as described above for Images/Video/Audio, but Android 9 on Xiaomi Mi A2 Lite behaves like this only with Audio files, while deleting Images/Video fine.

I have android:requestLegacyExternalStorage="false" in manifest.

Is there a way to force MediaStore to delete data on file system as well? Why is file on file system left behind?

Upvotes: 22

Views: 15519

Answers (5)

Perraco
Perraco

Reputation: 17380

I'm also experiencing the same issue, but with Huawei models where it fails with image & video files.

I've found that there are some reported bugs about it, although Google have discarded 2 of them because they can't reproduce it. I would advice that you star them and add some more details.

https://issuetracker.google.com/issues/157714528

https://issuetracker.google.com/issues/142270549

https://issuetracker.google.com/issues/145348304

Update:

I've created a new issue ticket, as some of the above have been discarded by google as not reproducible:

https://issuetracker.google.com/issues/184355104

Upvotes: 1

Bruno
Bruno

Reputation: 4527

I fixed this problem in my app for Android 10 by opting out of scoped storage (android:requestLegacyExternalStorage="true") and requesting WRITE_EXTERNAL_STORAGE for Android 10 as well:

<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="29"
    tools:ignore="ScopedStorage" />

Then ContentResolver.delete will not trigger RecoverableSecurityException and will also delete the file from disk. Given you requested and was granted Manifest.permission.WRITE_EXTERNAL_STORAGE permission.

Note: If you don't care about deleting files not created by your app or created by previous installations of your app, then you don't need to request that permission.

Upvotes: 0

AndroidDev
AndroidDev

Reputation: 1625

Yes, as you have pointed out that's how we had to delete media files. We have to delete the physical copy of the file by forming a File object and also delete the indexed file in MediaStore using ContentResolver.delete() (or) do a media scan on the deleted file which would remove it's entry in MediaStore.

This is how it used to work in below Android 10 os. And would still work the same in Android 10 as well if you had opted out of scoped storage by specifying it in manifest android:requestLegacyExternalStorage="true"

Now in Android 11 you are forced to use scoped storage. If you want to delete any media file which is not created by you, you have to get the permission from the user. You can get the permission using MediaStore.createDeleteRequest(). This will show a dialog by description what operation users are about to perform, once the permission is granted, android has an internal code to take care of deleting both the physical file and the entry in MediaStore.

private void requestDeletePermission(List<Uri> uriList){
  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
      PendingIntent pi = MediaStore.createDeleteRequest(mActivity.getContentResolver(), uriList);

      try {
         startIntentSenderForResult(pi.getIntentSender(), REQUEST_PERM_DELETE, null, 0, 0,
                  0);
      } catch (SendIntentException e) { }
    }
}

The above code would do both, requesting the permission to delete, and once permission granted delete the files as well.

And the callback result you would get it in onActivityResult()

Upvotes: 8

jazzbpn
jazzbpn

Reputation: 7368

Use this function to delete file using display name of the file: This func will delete MediaStore item and it's associated data on file system in Android-10 or Android-Q

Note: In my case I am working with files like MediaStore.Files.FileColumns..

public static boolean deleteFileUsingDisplayName(Context context, String displayName) {

    Uri uri = getUriFromDisplayName(context, displayName);
    if (uri != null) {
        final ContentResolver resolver = context.getContentResolver();
        String[] selectionArgsPdf = new String[]{displayName};

        try {
            resolver.delete(uri, MediaStore.Files.FileColumns.DISPLAY_NAME + "=?", selectionArgsPdf);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
            // show some alert message
        }
    }
    return false;

}

Use this function to get Uri from DisplayName

public static Uri getUriFromDisplayName(Context context, String displayName) {

    String[] projection;
    projection = new String[]{MediaStore.Files.FileColumns._ID};

    // TODO This will break if we have no matching item in the MediaStore.
    Cursor cursor = context.getContentResolver().query(extUri, projection,
            MediaStore.Files.FileColumns.DISPLAY_NAME + " LIKE ?", new String[]{displayName}, null);
    assert cursor != null;
    cursor.moveToFirst();

    if (cursor.getCount() > 0) {
        int columnIndex = cursor.getColumnIndex(projection[0]);
        long fileId = cursor.getLong(columnIndex);

        cursor.close();
        return Uri.parse(extUri.toString() + "/" + fileId);
    } else {
        return null;
    }

}

Upvotes: 6

Alejandro Gomez
Alejandro Gomez

Reputation: 205

According to my observations there is no force delete.

I usually add several checks if a file has really been deleted on Android Q it is also not possible to delete an entire album without the user manually confirming each file. this makes deleting files on the device uninteresting for me

Upvotes: 2

Related Questions