My final conclusions.
For APIs >= 29 is not possible to delete non-owned files without user interaction, and there is no way around this fact.
In Android 10/Q (API 29), a RecoverableSecurityException must be caught, then request the user permission, and finally if granted perform the deletion.
In Android 11/R (API 30) is greatly improved. Can delete in bulk even combining already owned files in the same batch. No need to handle anything after the request, the system takes care of the deletion if granted by the user.
The limitation is that it only handles media files (images, videos, audio). For other file types an IllegalArgumentException is thrown with the message: "All requested items must be referenced by specific ID", (check this message in MediaStore source code).
Note that in API 30 there is a new MANAGE_EXTERNAL_STORAGE permission, but its usage requires extra steps in the developer's console, such as to explain why the permission is needed.
Example:
public static void delete(final Activity activity, final Uri[] uriList, final int requestCode)
throws SecurityException, IntentSender.SendIntentException, IllegalArgumentException
{
final ContentResolver resolver = activity.getContentResolver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
{
// WARNING: if the URI isn't a MediaStore Uri and specifically
// only for media files (images, videos, audio) then the request
// will throw an IllegalArgumentException, with the message:
// 'All requested items must be referenced by specific ID'
// No need to handle 'onActivityResult' callback, when the system returns
// from the user permission prompt the files will be already deleted.
// Multiple 'owned' and 'not-owned' files can be combined in the
// same batch request. The system will automatically delete them
// using the same prompt dialog, making the experience homogeneous.
final List<Uri> list = new ArrayList<>();
Collections.addAll(list, uriList);
final PendingIntent pendingIntent = MediaStore.createDeleteRequest(resolver, list);
activity.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0, null);
}
else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
{
try
{
// In Android == Q a RecoverableSecurityException is thrown for not-owned.
// For a batch request the deletion will stop at the failed not-owned
// file, so you may want to restrict deletion in Android Q to only
// 1 file at a time, to make the experience less ugly.
// Fortunately this gets solved in Android R.
for (final Uri uri : uriList)
{
resolver.delete(uri, null, null);
}
}
catch (RecoverableSecurityException ex)
{
final IntentSender intent = ex.getUserAction()
.getActionIntent()
.getIntentSender();
// IMPORTANT: still need to perform the actual deletion
// as usual, so again getContentResolver().delete(...),
// in your 'onActivityResult' callback, as in Android Q
// all this extra code is necessary 'only' to get the permission,
// as the system doesn't perform any actual deletion at all.
// The onActivityResult doesn't have the target Uri, so you
// need to cache it somewhere.
activity.startIntentSenderForResult(intent, requestCode, null, 0, 0, 0, null);
}
}
else
{
// As usual for older APIs
for (final Uri uri : uriList)
{
resolver.delete(uri, null, null);
}
}
}
MediaStore
? something else? – ChieftainUri
values for those, and you would not have permissions for them anyway. However, if they areMediaStore
entries, you might be able to delete them using theMediaStore
itself, if you holdWRITE_EXTERNAL_STORAGE
. The docs cite being able to read the contents withREAD_EXTERNAL_STORAGE
. – Chieftain