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?
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." }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With