gpt4 book ai didi

android - 一些应用程序如何在没有 root 的情况下访问 Android 11 上 ".../Android/..."子文件夹的内容?

转载 作者:行者123 更新时间:2023-12-03 13:27:27 24 4
gpt4 key购买 nike

背景
various storage restrictions on Android 10 and 11 ,其中还包括一个新的权限(MANAGE_EXTERNAL_STORAGE)来访问所有文件(但它不允许访问真正的所有文件),而以前的存储权限被减少为仅授予对媒体文件的访问权限:

  • 应用程序可以自由访问“媒体”子文件夹。
  • 应用程序永远无法访问“数据”子文件夹,尤其是内容。
  • 对于“obb”文件夹,如果允许应用程序安装应用程序,它可以访问它(将文件复制到那里)。否则不能。
  • 使用 USB 或 root,您仍然可以访问它们,作为最终用户,您可以通过内置的文件管理器应用程序"file"访问它们。

  • 问题
    我注意到一个名为“ here”的应用程序以某种方式克服了这个限制( X-plore):一旦您进入“Android/data”文件夹,它会要求您授予对它的访问权限(以某种方式直接使用 SAF),并且当您授予它时,您可以访问“Android”文件夹的所有文件夹中的所有内容。
    这意味着可能仍然有办法达到它,但问题是由于某种原因,我无法制作出同样效果的样本。
    我发现并尝试过的
    看来这个应用程序的目标是 API 29 (Android 10),它还没有使用新的权限,它有标志 requestLegacyExternalStorage .我不知道他们使用的相同技巧在针对 API 30 时是否会起作用,但我可以说,在我的情况下,在带有 Android 11 的 Pixel 4 上运行,它工作正常。
    所以我试着做同样的事情:
  • 我制作了一个针对 Android API 29 的示例 POC,授予了(各种)存储权限,包括 legacy 标志。
  • 我试图直接请求访问“Android”文件夹(基于 here),遗憾的是它没有工作,因为它出于某种原因(继续进入 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)
    我尝试了各种标志组合。
  • 启动应用程序时,当我自己手动到达“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: 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")
    }
    因此,使用 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()
    但是,一旦您可以访问它,您将无法从中获取文件列表,不能通过 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()
    问题
  • 如何直接向“Android”文件夹请求一个 Intent,该文件夹实际上可以让我访问它,并让我获取子文件夹及其内容?
  • 还有另一种选择吗?也许使用 adb 和/或 root,我可以授予 SAF 访问此特定文件夹的权限?
  • 最佳答案

    以下是它在 X-plore 中的工作方式:
    何时开启 Build.VERSION.SDK_INT>=30 ,
    【内部存储】/Android/数据不可访问,java File.canRead()File.canWrite()返回 false,因此我们需要为该文件夹内的文件(可能还有 obb)切换到替代文件系统。
    您已经知道存储访问框架是如何工作的,所以我将详细说明需要做什么。
    您调用ContentResolver.getPersistedUriPermissions()查看您是否已保存此文件夹的权限。最初你没有它,所以你请求用户许可:
    要请求访问,请使用 startActivityForResultIntent(Intent.ACTION_OPEN_DOCUMENT_TREE).putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android"))在这里你设置EXTRA_INITIAL_URI该选择器应直接在主存储上的 Android 文件夹上启动,因为我们想要访问 Android 文件夹。当您的应用程序以 API30 为目标时,选择器将不允许选择存储根目录,并且通过获得对 Android 文件夹的权限,您可以同时使用 dataobb里面的文件夹,有一个权限请求。
    当用户点击 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} (与您在 onActivityResult 中获得的 Uri 相同)。从 getPersistedUriPermissions 迭代列表找到感兴趣的 uri,如果发现可以使用它,否则请用户授权。
    现在您想使用 ContentResolverDocumentsContract使用此“树”uri 和您在 Android 文件夹内的文件的相对路径。这是列出 data 的示例文件夹:data/是相对于授予的“树”uri 的路径。使用 DocumentsContract.buildChildDocumentsUriUsingTree() 构建最终 uri (列出文件)或DocumentsContract.buildDocumentUriUsingTree() (用于处理单个文件),例如: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 功能读取/写入/重命名/移动/删除/创建的类似方式,您可能已经知道,使用 ContentResolverDocumentsContract 的方法.
    一些细节:

  • 它不需要android.permission.MANAGE_EXTERNAL_STORAGE
  • 它适用于目标 API 29 或 30
  • 它仅适用于主存储,不适用于外部 SD 卡
  • 对于 data 文件夹内的所有文件,您需要使用 SAF(java 文件不起作用),只需使用相对于 Android 文件夹的文件层次结构
  • 将来谷歌可能会在他们的“安全” Intent 中修补这个漏洞,并且在一些安全更新后这可能无法工作

  • 编辑:示例代码,基于 Cheticamp Github sample .该示例显示了“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
    )
    }
    }

    关于android - 一些应用程序如何在没有 root 的情况下访问 Android 11 上 ".../Android/..."子文件夹的内容?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65967690/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com