Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Persisting URI permissions using takePersistableUriPermission not working in Jetpack Compose

I'm tring to save a file to external storage and for that I use ACTION_OPEN_DOCUMENT_TREE. All works fine, my issue is that it asks for permission to access the selected folder every time the user picks a folder. I want the user to be able to select any folder they want each time, but if they've already granted my app permission to access a folder, I don't want them to be prompted for permission again.

My code:

val saveToDevicePickFolderLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
        uri?.let {
            try {
                App.instance.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                Logger.i { "Permissions persisted for URI: $it" }
            } catch (e: SecurityException) {
                Logger.e { "Failed to take persistable URI permission: ${e.localizedMessage}" }
            }
            viewModel.saveToDevice(selectedFileList, it, context)
            toggleSelectMode(on = false)
        }
    }

Then when debugging it I've tested out everything possible when launching the ActivityResultLauncher:

saveFileListToDevice = { list ->
   
    val persistedUriPermission = App.instance.contentResolver.persistedUriPermissions.firstOrNull()
    val persistedUri: Uri? = persistedUriPermission?.uri
    Logger.i { "Retrieved persisted URI $persistedUri with read? ${persistedUriPermission?.isReadPermission} and write? ${persistedUriPermission?.isWritePermission} permissions" }

    if (persistedUri != null) {
        try {
            // Ensure persistable URI permissions are granted for the persisted URI
            App.instance.contentResolver.takePersistableUriPermission(
                persistedUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )
            Logger.i { "Permissions persisted for URI: $persistedUri" }
        } catch (e: SecurityException) {
            Logger.i { "Failed to take persistable URI permission: ${e.localizedMessage}" }
        }

        // Check if external storage is mounted and accessible
        when (val storageState = Environment.getExternalStorageState(File(persistedUri.path))) {
            Environment.MEDIA_MOUNTED -> {
                Logger.i { "External storage is mounted and writable." }
            }
            Environment.MEDIA_MOUNTED_READ_ONLY -> {
                Logger.i { "External storage is mounted, but read-only." }
            }
            Environment.MEDIA_UNMOUNTED, Environment.MEDIA_REMOVED -> {
                Logger.i { "External storage is not available." }
            }
            else -> {
                Logger.i { "Unknown state for external storage: $storageState" }
            }
        }

        val documentFile = DocumentFile.fromTreeUri(context, persistedUri)
        if (documentFile != null && documentFile.exists()) {
            // The URI is valid and the document exists
            Logger.i { "Document exists or URI is VALID" }
            saveToDevicePickFolderLauncher.launch(persistedUri)
        } else {
            Logger.i { "Document does not exist or URI is invalid" }
            saveToDevicePickFolderLauncher.launch(null)
        }
    } else {
        Logger.i { "Opening for input null" }
        saveToDevicePickFolderLauncher.launch(null)
    }
},

The logs I get are:

Permissions persisted for URI: content://com.android.externalstorage.documents/tree/primary%3ADownload%2FA
Retrieved persisted URI UriPermission {uri=content://com.android.externalstorage.documents/tree/primary%3ADownload%2FA, modeFlags=3, persistedTime=1732635607603} with read? true and write? true permissions
Permissions persisted for URI: content://com.android.externalstorage.documents/tree/primary%3ADownload%2FA
Unknown state for external storage: unknown
Document exists or URI is VALID
Permissions persisted for URI: content://com.android.externalstorage.documents/tree/primary%3ADownload%2FA

I've also tried persisting the URI to SharedPreferences but it makes no difference. My checkpoints so far are:

1.Verified that takePersistableUriPermission() is called correctly both when selecting and accessing the folder. ✅

2.Checked persisted URIs using contentResolver.persistedUriPermissions to confirm both read and write permissions were granted. ✅

3.Checked if the URI is still valid using DocumentFile.fromTreeUri(). ✅

4.Checked the external storage state, but received state Unknown ❌ which might be the cause for this, I don't understand what can I do here?

like image 955
Monica Ivan Avatar asked Jan 19 '26 08:01

Monica Ivan


1 Answers

You may be able to check whether the folder is accesible and writeable using the following code without relying on physical storage API:

val documentFile = DocumentFile.fromTreeUri(context, persistedUri)

if (documentFile != null && documentFile.exists() && documentFile.canWrite()) {
    Logger.i { "Document exists and is writable." }
} else if (documentFile != null && documentFile.exists()) {
    Logger.i { "Document exists but is read-only." }
} else {
    Logger.i { "Document does not exist or URI is invalid." }
}
like image 178
Ali Khakbaz Avatar answered Jan 20 '26 22:01

Ali Khakbaz