I'm currently migrating an Android app to Scoped Storage. Moving away from java.io.file
is a real nightmare, and feels much of a drawback to me. I'm currently trying to understand if paths returned by getExternalMediaDirs()
can effectively be used with direct file paths.
After asking user to select a given folder
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
and storing persistent Uri
permissions to read/write in onActivityResult
getContentResolver().takePersistableUriPermission(data.getData(), (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
Is it expected to still not being able to use java.io.File
in such granted path?
SAF path:
content://com.android.externalstorage.documents/tree/primary%3AAndroid/media/com.otherapp/test.txt
equivalent to java.io.File
path
/storage/emulated/0/Android/media/com.otherapp/test.txt
Some final notes:
java.io.File
path (EACCES
exception)/Android/media/com.otherapp
is different than self-attributed /Android/media/com.app
android.permission.MANAGE_EXTERNAL_STORAGE
not availablethanks nicola
Yes, because after granting permissions you have access to Content Scheme Uri
of that specific folder not file system paths so you will be unable to list java.io.File objects using this Uri.
But you can use it to listFiles()
which will return you a list of DocumentFile. You can then use these DocumentFile
objects to get Uri
of that specific file using documentFile.getUri() . Using that Uri
you can easily perform basic operations like display,copy,delete,share
on that file.
Below is an example of listing files from a folder of other app.
I am skiping take permissions part of code assuming that you already have permissions to access that other app's folder
In this example i am listing files from Android/media/com.whatsapp/WhatsApp/Media/.Statuses
DOC Ids are
companion object {
const val ANDROID_DOCID = "primary:Android/media/"
const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
After getting permission we have tree Uri
Here tree Uri is content://com.android.externalstorage.documents/tree/primary:Android/media
val dir = DocumentFile.fromTreeUri(
context,
treeUri!!
)
This dir is a DocumentFile, now using this to list all folders inside that tree Uri.
val selectedPackageName = “com.whatsapp”
val FOLDER_NAME_STATUSES = ".Statuses”;
val selectedRootName = “WhatsApp"
var waStatus: DocumentFile? = null
val statusesFiles = arrayListOf<DocumentFile>()
dir?.listFiles()?.forEach {
if (it.name.equals(selectedPackageName)) {
it.listFiles().forEach {
if (it.name.equals(selectedRootName)) {
it.listFiles().forEach {
if (it.name.equals(Constants.FOLDER_NAME_STATUSES)) {
waStatus = it
statusesFiles.addAll(waStatus!!.listFiles().filter {
it.mimeType.equals(Constants.MIME_TYPE_IMG_PNG) || it.mimeType.equals(
Constants.MIME_TYPE_IMG_JPG
) || it.mimeType.equals(Constants.MIME_TYPE_IMG_JPEG)
}.sortedByDescending { it.lastModified() })
}
}
}
}
}
}
Now we have a list of DocumentFile
from folder of another app. Use Uri
of this file for further operations like display,copy,delete,share
.
Also, read comments your point about Downloads
folder but would this be the same matter for files in Download
Downloads is a public directory, i listed files which are created by my own app using listFiles() and it returned me list of File.
val folder = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
Constants.DOWNLOADS_APP_DIRECTORY_NAME
)
if (folder != null && folder.exists()) {
val newFiles: Array<File> = statusFolderInDownloads.listFiles({ file ->
getFileType(file.path) == FILETYPE.IMAGE || getFileType(file.path) == FILETYPE.VIDEO
})
list.addAll(newFiles)
list.sortByDescending { it.lastModified() }
if (list.isNotEmpty()) {
return list
} else {
return arrayListOf()
}
}