and*_*per 13 android storage-access-framework android-11
Android 10 和 11有各种存储限制,其中还包括访问所有文件的新权限 ( MANAGE_EXTERNAL_STORAGE )(但它不允许访问真正的所有文件),而之前的存储权限已减少以仅授予对媒体的访问权限文件:
我注意到一个名为“ X-plore ”的应用程序以某种方式克服了这个限制(此处):一旦您进入“Android/data”文件夹,它会要求您授予对它的访问权限(直接使用 SAF,不知何故),并且当您授予它,您可以访问“Android”文件夹的所有文件夹中的所有内容。
这意味着可能仍然有一种方法可以达到它,但问题是由于某种原因,我无法制作具有相同效果的样本。
该应用程序似乎针对 API 29 (Android 10),并且它尚未使用新权限,并且它具有标志requestLegacyExternalStorage。我不知道他们在针对 API 30 时使用的相同技巧是否有效,但我可以说,在我的情况下,在 Pixel 4 和 Android 11 上运行,它工作正常。
所以我尝试做同样的事情:
我制作了一个针对 Android API 29 的示例 POC,已授予(各种)存储权限,包括遗留标志。
我试图请求直接访问“Android”文件夹(基于此处),但遗憾的是由于某种原因无法正常工作(一直转到 DCIM 文件夹,不知道为什么):
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)
Run Code Online (Sandbox Code Playgroud)
我尝试了各种标志组合。
启动应用程序时,当我自己手动到达“Android”文件夹时,因为这不能正常工作,我授予了对该文件夹的访问权限,就像在其他应用程序上一样。
获取结果时,我尝试获取路径中的文件和文件夹,但无法获取它们:
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: /sf/ask/3966034761/
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")
}
Run Code Online (Sandbox Code Playgroud)
因此,使用 DocumentFile.fromTreeUri 我仍然可以获得无用的“media”文件夹,而使用 File 类我只能看到还有“data”和“obb”文件夹,但仍然无法访问它们的内容......
所以这根本不起作用。
后来我发现了另一个使用这个技巧的应用程序,叫做“ MiXplorer ”。在这个应用程序上,它无法直接请求“Android”文件夹(也许它甚至没有尝试过),但是一旦您允许,它就会授予您对其及其子文件夹的完全访问权限。而且,它以 API 30 为目标,所以这意味着它不能仅仅因为您以 API 29 为目标而工作。
我注意到(有人写给我)通过对代码进行一些更改,我可以分别请求访问每个子文件夹(意味着对“数据”的请求和对“obb”的新请求),但这是不是我在这里看到的,应用程序所做的。
意思是,为了进入“Android”文件夹,我使用这个 Uri 作为 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()
Run Code Online (Sandbox Code Playgroud)
但是,一旦获得对它的访问权限,您将无法从中获取文件列表,不能通过 File 也不能通过 SAF。
但是,正如我所写的,奇怪的是,如果您尝试类似的操作,而不是访问“Android/data”,您将能够获取其内容:
val androidDataUri=Uri.Builder().scheme("content").authority("com.android.externalstorage.documents")
.appendEncodedPath("tree").appendPath("primary:").appendPath("document").appendPath("primary:Android/data").build()
Run Code Online (Sandbox Code Playgroud)
Poi*_*ull 13
以下是它在 X-plore 中的工作原理:
当 on 时Build.VERSION.SDK_INT>=30,[Internal storage]/Android/data 不可访问,javaFile.canRead()或File.canWrite()返回 false,因此我们需要切换到此文件夹内文件的备用文件系统(也可能还有 obb)。
您已经知道存储访问框架的工作原理,因此我将详细说明需要完成的工作。
您致电ContentResolver.getPersistedUriPermissions()以了解您是否已经拥有此文件夹的保存权限。最初你没有它,所以你请求用户许可:
要请求访问,请使用startActivityForResultwith Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android"))
Here 您设置的EXTRA_INITIAL_URI选择器应直接在主存储上的 Android 文件夹上启动,因为我们要访问 Android 文件夹。当您的应用以 API30 为目标时,picker 将不允许选择存储的根目录,并且通过获得对 Android 文件夹的权限,您可以使用一个权限请求来处理里面的文件夹data和obb文件夹。
当用户通过 2 次点击确认时,onActivityResult您将在数据中获得 Uri,该数据应为content://com.android.externalstorage.documents/tree/primary%3AAndroid. 进行必要的检查以验证用户是否确认了正确的文件夹。然后调用contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION)保存权限,你就准备好了。
所以我们回到ContentResolver.getPersistedUriPermissions(),其中包含已授予权限的列表(可能还有更多),您在上面授予的权限如下所示:(UriPermission {uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid, modeFlags=3}与您在 中获得的 Uri 相同onActivityResult)。迭代列表getPersistedUriPermissions以找到感兴趣的 uri,如果找到可以使用它,否则请求用户授权。
现在,您想要处理ContentResolver和DocumentsContract使用此“树”uri 以及 Android 文件夹内文件的相对路径。这是列出data文件夹的示例:
data/是相对于授予的“树”uri 的路径。使用DocumentsContract.buildChildDocumentsUriUsingTree()(列出文件)或DocumentsContract.buildDocumentUriUsingTree()(用于处理单个文件)构建最终 uri ,例如:DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri), DocumentsContract.getTreeDocumentId(treeUri)+"/data/"),您将获得 uri=content://com.android.externalstorage.documents/tree/primary%3AAndroid/document/primary%3AAndroid%2Fdata%2F/children适合列出数据文件夹中的文件。现在调用ContentResolver.query(uri, ...)并处理数据Cursor以获取文件夹列表。
你与其他SAF功能的读/写/重命名/移动/删除工作类似的方式/创建,你可能已经知道,使用ContentResolver或方法DocumentsContract。
一些细节:
android.permission.MANAGE_EXTERNAL_STORAGE编辑:示例代码,基于 Cheticamp Github 示例。该示例显示了“Android”文件夹的每个子文件夹的内容(和文件数):
class MainActivity : AppCompatActivity() {
private val handleIntentActivityResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode != Activity.RESULT_OK)
return@registerForActivityResult
val directoryUri = it.data?.data ?: return@registerForActivityResult
contentResolver.takePersistableUriPermission(
directoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
if (checkIfGotAccess())
onGotAccess()
else
Log.d("AppLog", "you didn't grant permission to the correct folder")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
val openDirectoryButton = findViewById<FloatingActionButton>(R.id.fab_open_directory)
openDirectoryButton.setOnClickListener {
openDirectory()
}
}
private fun checkIfGotAccess(): Boolean {
return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
} >= 0
}
private fun onGotAccess() {
Log.d("AppLog", "got access to Android folder. showing content of each folder:")
@Suppress("DEPRECATION")
File(Environment.getExternalStorageDirectory(), "Android").listFiles()?.forEach { androidSubFolder ->
val docId = "$ANDROID_DOCID/${androidSubFolder.name}"
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(androidTreeUri, docId)
val contentResolver = this.contentResolver
Log.d("AppLog", "content of:${androidSubFolder.absolutePath} :")
contentResolver.query(childrenUri, null, null, null)
?.use { cursor ->
val filesCount = cursor.count
Log.d("AppLog", "filesCount:$filesCount")
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val mimeIndex = cursor.getColumnIndex("mime_type")
while (cursor.moveToNext()) {
val displayName = cursor.getString(nameIndex)
val mimeType = cursor.getString(mimeIndex)
Log.d("AppLog", " $displayName isFolder?${mimeType == DocumentsContract.Document.MIME_TYPE_DIR}")
}
}
}
}
private fun openDirectory() {
if (checkIfGotAccess())
onGotAccess()
else {
val primaryStorageVolume = (getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
val intent =
primaryStorageVolume.createOpenDocumentTreeIntent().putExtra(EXTRA_INITIAL_URI, androidUri)
handleIntentActivityResult.launch(intent)
}
}
companion object {
private const val ANDROID_DOCID = "primary:Android"
private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
private val androidUri = DocumentsContract.buildDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
private val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
)
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3691 次 |
| 最近记录: |