Background
There are various storage restrictions on Android 10 and 11, which also includes a new permission (MANAGE_EXTERNAL_STORAGE) to access all files (yet it doesn't allow access to really all files ) while the previous storage permission got reduced to grant access just to media files :
- Apps can reach the "media" sub folder freely.
- Apps can never reach "data" sub folder and especially the content.
- For "obb" folder, if the app was allowed to install apps, it can reach it (to copy files to there). Otherwise it can't.
- Using USB or root, you could still reach them, and as an end user you can reach them via the built-in file-manager app "Files".
The problem
I've noticed an app that somehow overcome this limitation (here) called "X-plore": Once you enter "Android/data" folder, it asks you to grant access to it (directly using SAF, somehow), and when you grant it, you can access everything in all folders of "Android" folder.
This means there might still be a way to reach it, but problem is that I couldn't make a sample that does the same, for some reason.
What I've found and tried
It seems this app targets API 29 (Android 10), and that it doesn't use the new permission yet, and that it has the flag requestLegacyExternalStorage. I don't know if the same trick they use will work when targeting API 30, but I can say that on my case, running on Pixel 4 with Android 11, it works fine.
So I tried to do the same:
I made a sample POC that targets Android API 29, has storage permissions (of all kinds) granted, including the legacy flag.
I tried to request access directly to "Android" folder (based on here), which sadly didn't work as it goes to some reason (kept going to DCIM folder, no idea why) :
val androidFolderDocumentFile = DocumentFile.fromFile(File(primaryVolume.directory!!, "Android"))
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidFolderDocumentFile.uri)
startActivityForResult(intent, 1)
I tried various flags combinations.
When launching the app, when I reach the "Android" folder myself manually as this didn't work well, and I granted the access to this folder just like on the other app.
When getting the result, I try to fetch the files and folders in the path, but it fails to get them:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AppLog", "resultCode:$resultCode")
val uri = data?.data ?: return
if (!DocumentFile.isDocumentUri(this, uri))
return
grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val fullPathFromTreeUri = FileUtilEx.getFullPathFromTreeUri(this, uri) // code for this here: https://stackoverflow.com/q/56657639/878126
val documentFile = DocumentFile.fromTreeUri(this, uri)
val listFiles: Array<DocumentFile> = documentFile!!.listFiles() // this returns just an array of a single folder ("media")
val androidFolder = File(fullPathFromTreeUri)
androidFolder.listFiles()?.forEach {
Log.d("AppLog", "${it.absoluteFile} children:${it.listFiles()?.joinToString()}") //this does find the folders, but can't reach their contents
}
Log.d("AppLog", "granted uri:$uri $fullPathFromTreeUri")
}
So using DocumentFile.fromTreeUri I could still get just "media" folder which is useless, and using the File class I could only see there are also "data" and "obb" folders, but still couldn't reach their contents...
So this didn't work well at all.
Later I've found out another app that uses this trick, called "MiXplorer". On this app, it failed to request "Android" folder directly (maybe it didn't even try), but it does grant you full access to it and its sub-folders once you allow it. And, it targets API 30, so this means it's not working just because you target API 29.
I've noticed (someone wrote me) that with some changes to the code, I could request access to each of the sub-folders separately (meaning a request for "data" and a new request for "obb"), but this is not what I see here, that apps do.
Meaning, to get to "Android" folder, I get use this Uri as a parameter for Intent.EXTRA_INITIAL_URI :
val androidUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android").build()
However, once you get an access to it, you won't be able to get the list of files from it, not via File, and not via SAF.
But, as I wrote, the weird thing is that if you try something similar, of getting to "Android/data" instead, you will be able to get its content:
val androidDataUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android/data").build()
The questions
- How can I request an Intent directly to "Android" folder that will actually let me access to it, and let me get the sub-folders and their contents?
- Is there another alternative for this? Maybe using adb and/or root, I could grant SAF access to this specific folder ?