Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to delete audio files from the device's external storage (for example in the /storage/emulated/0/Music folder). After analyzing the MediaStore sample, I ended up with the following solution for API 28 and earlier:

fun deleteTracks(trackIds: LongArray): Int {
    val whereClause = buildWildcardInClause(trackIds.size) // _id IN (?, ?, ?, ...)
    return resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, whereClause, Array(trackIds.size) { trackIds[it].toString() })
}

I noticed that the above code only deletes tracks from the MediaStore database but not from the device (files are re-added to the MediaStore after rebooting). So I modified that code to query the Media.DATA column, then used that information to delete the associated files. This works as intended.

But now that Android Q introduced Scoped Storage, Media.DATA (and Albums.ALBUM_ART) are now deprecated because the app may not be able to access those files. ContentResolver.openFileDescriptor can only be used to read files, not delete them.

Then what is the recommended way to delete tracks as of Android Q ? The sample does not show how to delete multiple files from the MediaStore, and MediaStore.Audio.Media seems to work differently than MediaStore.Images.Media.

like image 516
Thibault Seisel Avatar asked Oct 08 '19 09:10

Thibault Seisel


People also ask

How do I delete multiple files in a folder?

Delete key You can delete multiple files or folders by holding down Ctrl and clicking each file or folder before pressing Delete . You can hold down Shift while pressing Delete to prevent files from going to the Recycle Bin when deleted.

How do I get a list of audio files in a specific folder on Android?

id. mylist); myList = new ArrayList<String>(); File directory = Environment. getExternalStorageDirectory(); file = new File( directory + "/Test" ); File list[] = file. listFiles(); for( int i=0; i< list.


2 Answers

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.

like image 68
Nicole Borrelli Avatar answered Sep 19 '22 13:09

Nicole Borrelli


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

like image 44
Velu Avatar answered Sep 18 '22 13:09

Velu