0

First of all there are a couple similar questions have been asked and neither solve the problem I'm facing - this is for Android 11 ONLY. Behaviour on Android 12 and 13 works without issue.

Disregarded questions and answers :

java.lang.IllegalArgumentException: All requested items must be referenced by specific ID (Android)

I'm trying to delete or modify a media playlist file my app didn't create, in order to do this for Android 11 onwards I use some code, which is illustrated in the documentation (https://developer.android.com/training/data-storage/shared/media#update-other-apps-files) :

@Composable
fun DeletePlaylistButton(
    playlistId : Long,
    onDeleteCompleted: () -> Unit
) {
    val context = LocalContext.current.applicationContext
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartIntentSenderForResult(),
        onResult = { if (it.resultCode == Activity.RESULT_OK) onDeleteCompleted() })


    Button(onClick = {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            val pi = MediaStore.createDeleteRequest(context.contentResolver, listOf(ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, playlistId)))
            val request = IntentSenderRequest.Builder(pi.intentSender).build()
            launcher.launch(request)
        }
    }) {
        Text("Delete")
    }
}

This code works without issue on Android 12 and Android 13, a dialog appears requesting to delete the playlist. However on Android 11 get a crash :

java.lang.IllegalArgumentException: All requested items must be referenced by specific ID
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
    at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
    at android.content.ContentProviderProxy.call(ContentProviderNative.java:732)
    at android.content.ContentResolver.call(ContentResolver.java:2405)
    at android.provider.MediaStore.createRequest(MediaStore.java:822)
    at android.provider.MediaStore.createDeleteRequest(MediaStore.java:985)

I am using Pixel 6 emulators running API's 29 (this has different strategy as MediaStore.createXXXXXRequest was only introduced in API 30), 30, 32, 33. The only device that fails to work on is Android 11.

The files pushed to each device are identical and I have pulled the media store databases off the emulators, and I am definitely referencing the correct playlist id from the database.

The Uri I am trying to create a delete request for :

content://media/external/audio/playlists/734

This matches the item in the pulled db :

Pulled database audio_playlists t

On first glance seems like a bug with Android 11 runtime, not the Android SDK, but would be nice if anyone can see something incorrect with how I've coded this, or had simialr experience before I start looking into MediaProvider.java source code for Android 11. If this is indeed a bug with the runtime then I'll have to essentially remove the feature for Android 11 users.

It's worth noting between Android 10, 11, 12 and 13 the location for the media store database changed :

  • Android 10 : /data/data/com.android.providers.media/databases

  • Android 11 : /data/data/com.android.providers.media.module/databases

  • Android 12/13 : /data/data/com.google.android.providers.media.module/databases

It seems between Android 10 and 11 there was some package restructuring with various files moving locations and a lot of modifications, which leads to me to think there is a bug with Android 11 source. There is also subtle schema changes here and there, but nothing that should break the provider in this way.

I cannot use the permission android.permission.MANAGE_EXTERNAL_STORAGE, which would solve all mess Android has created around file access to begin with, as the application will not pass Google Play review. Currently my only option, without further investigation, is to remove a feature from the application, specfically for Android 11 users, which I'd rather not do.

Note

I am using similar pattern to request write requests, via various content://xxx Uris for other media items in different database tables, like the audio table, these requests work fine on all emulators, these is just for playlists.

Issue Tracker

Although this linked issue starts out with the user having the wrong permissions for Android 11 subsequent conversation shows the user tried to do the same as myself and faced the same issue. However even though this was expressed by the OP Google closed the issue down : https://issuetracker.google.com/issues/171338916

Any help appreciated!

Mark
  • 9,604
  • 5
  • 36
  • 64

1 Answers1

0

Ok so turns out looking at the Google AOSP source there is a definite bug with Android 11.

As I suspected this is in MediaProvider. When using a ContentResolver to resolve the requests :

MediaStore.createWriteRequest(context.contentResolver, listOf(ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, playlistId)))
        
MediaStore.createDeleteRequest(context.contentResolver, listOf(ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, playlistId)))
        
MediaStore.createTrashRequest(context.contentResolver, listOf(ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, playlistId)), true)

MediaStore.createFavoriteRequest(context.contentResolver, listOf(ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, playlistId)), true)

Then this resolves to MediaProvider.java on the device at runtime.

For Android 11 the source code, when checking the request matches Uris correctly, looks like this :

private @NonNull PendingIntent createRequest(@NonNull String method, @NonNull Bundle extras) {
        final ClipData clipData = extras.getParcelable(MediaStore.EXTRA_CLIP_DATA);
        final List<Uri> uris = collectUris(clipData);
        for (Uri uri : uris) {
            final int match = matchUri(uri, false);
            switch (match) {
                case IMAGES_MEDIA_ID:
                case AUDIO_MEDIA_ID:
                case VIDEO_MEDIA_ID:
                    // Caller is requesting a specific media item by its ID,
                    // which means it's valid for requests
                    break;
                default:
                    throw new IllegalArgumentException(
                            "All requested items must be referenced by specific ID");
            }
        } ....

As you can see if the Uri is not matched to either of IMAGES_MEDIA_ID,IMAGES_MEDIA_ID or VIDEO_MEDIA_ID then an error is thrown and marshalled back to the caller - as per the stacktrace I get.

However source for Android 12L looks like this :

   private @NonNull PendingIntent createRequest(@NonNull String method, @NonNull Bundle extras) {
        final ClipData clipData = extras.getParcelable(MediaStore.EXTRA_CLIP_DATA);
        final List<Uri> uris = collectUris(clipData);
        for (Uri uri : uris) {
            final int match = matchUri(uri, false);
            switch (match) {
                case IMAGES_MEDIA_ID:
                case AUDIO_MEDIA_ID:
                case VIDEO_MEDIA_ID:
                case AUDIO_PLAYLISTS_ID:
                    // Caller is requesting a specific media item by its ID,
                    // which means it's valid for requests
                    break;
                case FILES_ID:
                    // Allow only subtitle files
                    if (!isSubtitleFile(uri)) {
                        throw new IllegalArgumentException(
                                "All requested items must be Media items");
                    }
                    break;
                default:
                    throw new IllegalArgumentException(
                            "All requested items must be referenced by specific ID");
            } ....

As you can see AUDIO_PLAYLISTS_ID has now been added as a Uri matcher - it seems they found that this was an issue and fixed - shame the documentation doesn't have this caveat.

So for Android 11 it is impossible to follow documentation guidelines to perform any of the MediaStore.createXXXRequest(...) for a playlist media id without it crashing.

The fallout - you cannot modify playlst files you didn't create in Android 11. You will be required to have special handling for Android 11 to make sure you have access to a the file first, you can use, for this specific use case around playlists, something like :

fun Context.hasWritePermission(playlistUri: Uri) =
   checkUriPermission(
        playlistUri,
        Process.myPid(),
        Process.myUid(),
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
   ) == PackageManager.PERMISSION_GRANTED

However it might be more complicated as you'll probably need to use the File api to delete/modify a file, and again this would be special handling for Android 11 only ...

Mark
  • 9,604
  • 5
  • 36
  • 64