androidandroid-contentresolverstorage-access-frameworkandroid-storage

ContentResolver.takePersistableUriPermission: SecurityException No persistable permission grants found for UID 10434 and Uri content:/


I have noticed the following exception (Firebase Crashlytics):

Fatal Exception: java.lang.RuntimeException
Failure delivering result ResultInfo{who=null, request=1915322883, result=-1, data=Intent { dat=content:/... flg=0xc3 }} to activity {com..../com....Activity}: java.lang.SecurityException: No persistable permission grants found for UID 10571 and Uri content:/...
Fatal Exception: java.lang.SecurityException
No persistable permission grants found for UID 10434 and Uri content:/...

android.content.ContentResolver.takePersistableUriPermission (ContentResolver.java:2952)

Caused by android.os.RemoteException
Remote stack trace: at com.android.server.uri.UriGrantsManagerService.takePersistableUriPermission(UriGrantsManagerService.java:385) at android.app.IUriGrantsManager$Stub.onTransact(IUriGrantsManager.java:139) at android.os.Binder.execTransactInternal(Binder.java:1375) at android.os.Binder.execTransact(Binder.java:1311)

When the app calls ContentResolver.takePersistableUriPermission:

val safResultLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.StartActivityForResult()
) { activityResult ->
    coroutineScope.launch {
        if (activityResult.resultCode == Activity.RESULT_OK) {
            val treeUri = activityResult.data?.data
            if (treeUri != null) {
                context.contentResolver.takePersistableUriPermission(
                    treeUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                )
            }
        }
    }
}

// select a folder
try {
    safResultLauncher.launch(Intent.ACTION_OPEN_DOCUMENT_TREE)
} catch (e: Throwable) {
    // ...
}   

Happens only for Samsung devices (Android 13-14):

Update

So I guess we may try to clear current persistent uri grants (when you don't need it) because of limitation and also handle the exception in case when it's not possible to take persistable uri for some storages:

fun grantPermissionForFolderSaf(
    context: Context,
    activityResult: ActivityResult,
): Uri? {
    if (activityResult.resultCode == Activity.RESULT_OK) {
        val treeUri = activityResult.data?.data
        if (treeUri != null) {
            clearAllPersistedUriPermissions(context)
            try {
                context.contentResolver.takePersistableUriPermission(
                    /* uri = */ treeUri,
                    /* modeFlags = */ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                )
                return treeUri
            } catch (e: Throwable) {
                // ... explanation for a user to try to select a different storage
            }
        }
    }
    return null
}

private fun clearAllPersistedUriPermissions(context: Context) {
    try {
        val contentResolver = context.contentResolver
        for (uriPermission in contentResolver.persistedUriPermissions) {
            contentResolver.releasePersistableUriPermission(
                /* uri = */ uriPermission.uri,
                /* modeFlags = */ Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
        }
    } catch (e: Throwable) {
        // just to be safe...
        e.printStackTrace()
    }
}

Solution

  • There is no guarantee that you will get a persistable permission when you ask for one. There are only so many that your app can request (limit used to be 128 but should be 512 for your devices). Also, the manufacturer or DocumentsProvider developer might not offer persistable permissions for some choice that the user made.

    You need to gracefully handle the case where takePersistableUriPermission() throws an exception.