Scoped Storage: how to delete multiple audio files via MediaStore?

In Android 11 deleting/updating multiple media files is supported with the better and cleaner apis.

Prior to Android 10, we have to delete the physical copy of file by forming 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 describing 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 result you would get it in onActivityResult()


There are two ways to do this in Android 10, which depends on the value of android:requestLegacyExternalStorage in the app's AndroidManifest.xml file.

Option 1: Scoped Storage enabled (android:requestLegacyExternalStorage="false")

If your app added the content to begin with (using contentResolver.insert) then the method above (using contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray)) should work. On Android 10, any content submitted to Media Store by an app can be freely manipulated by that app.

For content the user added, or which belongs to another app, the process is a bit more involved.

To delete a single item from Media Store, don't pass the generic Media Store URI to delete (i.e.: MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), but rather, use the item's specific URI.

context.contentResolver.delete(
    audioContentUri,
    "${MediaStore.Audio.Media._ID} = ?",
    arrayOf(audioContentId)

(I'm pretty sure the 'where' clause there is not necessary, but it made sense to me :)

On Android 10 this will likely throw a RecoverableSecurityException, if your targetSdkVersion is >=29. Inside this exception is an IntentSender which can be started by your Activity to request permission from the user to modify or delete it. (The sender is found in recoverableSecurityException.userAction.actionIntent.intentSender)

Because the user must grant permission to each item, it's not possible to delete them in bulk. (Or, it's probably possible to request permission to modify or delete the entire album, one song at a time, and then use the delete query you use above. At that point your app would have permission to delete them, and Media Store would do that all at once. But you have to ask for each song first.)

Option 2: Legacy Storage enabled (android:requestLegacyExternalStorage="true")

In this case, simply calling contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, idsArray) not only removes the items from the database, but also deletes the files.

The app needs to be holding WRITE_EXTERNAL_STORAGE permission for this to work, but I tested the code you have in the question and verified that the files were also deleted.

So, for older API versions, I think you need to use contentResolver.delete and unlink the file yourself (or unlink the file and submit a request for it to be rescanned via MediaScannerConnection).

For Android 10+, contentResolver.delete will both remove the index AND the content itself.