存储访问框架-权限被拒绝问题,即使授予权限也是如此 [英] Storage Access Framework- Permission denied issue even when permissions granted

查看:21
本文介绍了存储访问框架-权限被拒绝问题,即使授予权限也是如此的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在实施存储访问框架以授予Android文件夹访问权限。我已经更新了targetSDKVersion=30,在Android 11上我遇到了存储权限问题。我希望获得访问Android/media/com.whatsapp/WhatsApp/Media中文件的存储权限。

为了使存储权限起作用,我实现了存储访问框架,以获得Android/media/com.whatsapp/WhatsApp/Media访问权限,然后获取其子文件夹中的文件(即状态、WhatsAppImages)

下面是代码详细信息

     <uses-permission android:name="android.permission.CAMERA" />

    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.ACTION_MANAGE_OVERLAY_PERMISSION" />

 <application
        android:name=".application.MyApplication"
        android:allowBackup="false"
        android:hardwareAccelerated="true"
        android:label="@string/app_name"
        android:largeHeap="true"
        android:requestLegacyExternalStorage="true" // also tried removing this
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:replace="android:allowBackup">

活动代码

   //First i checked and requested android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE

if ((ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    )
            == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )
            == PackageManager.PERMISSION_GRANTED)
) {
    //further work of opening directory
} else {

    TedPermission.with(this)
        .setPermissionListener(sdk30PermissionListener)
        .setDeniedMessage("If you reject permission,you can not use this service

Please turn on permissions at [Setting] > [Permission]")
        .setPermissions(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
        .check()
}

//This is the root path i want to hit to access all subfolders inside this one

public static final String whatsApp_root_path = "Android/media/com.whatsapp/WhatsApp/Media";
companion object {
   const val ANDROID_DOCID = "primary:${Constants.whatsApp_root_path}"
    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
    )
}

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()) {
            premiumDialogHandling()
            onGotAccess()
        } else {
            Log.d("AppLog", "you didn't grant permission to the correct folder")
            tinyDB.putBoolean("SDK30Permissions", false)
            showWrongFolderSelection()
        }
    }
private fun checkIfGotAccess(): Boolean {
    return contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
        uriPermission.uri.equals(androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
    } >= 0
}
private fun openDirectory() {
    if (checkIfGotAccess()) {
        onGotAccess()
    } else {
        val primaryStorageVolume =
            (getSystemService(STORAGE_SERVICE) as StorageManager).primaryStorageVolume
        val intent = primaryStorageVolume.createOpenDocumentTreeIntent()
            .putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidUri)
        handleIntentActivityResult.launch(intent)

    }
}


private fun onGotAccess() {
    tinyDB.putBoolean("SDK30Permissions", true)
//once user chooses ‘Use Folder’ to allow permissions
  processStatusFetchExecute()


}
public fun processStatusFetchExecute(completed: () -> Unit) {
    Timber.e("processStatusFetch:Init")

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        @Suppress("DEPRECATION")
       var statusesFolder = File(
            Environment.getExternalStorageDirectory(),
            Constants.whatsApp_root_path
        ).listFiles(FileFilter { file -> file.name.equals("${Constants.FOLDER_NAME_STATUSES}") })

        val docIdStatuses = "${HomeActivity.ANDROID_DOCID}/${statusesFolder.get(0).name}"
        val childrenUriStatuses =
            DocumentsContract.buildChildDocumentsUriUsingTree(
                HomeActivity.androidTreeUri,
                docIdStatuses
            )

        val statusTreeUri = DocumentsContract.buildTreeDocumentUri(
            HomeActivity.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, docIdStatuses
        )

        val hasAccess: Boolean =
            contentResolver.persistedUriPermissions.indexOfFirst { uriPermission ->
                uriPermission.uri.equals( HomeActivity.androidTreeUri) && uriPermission.isReadPermission && uriPermission.isWritePermission
            } >= 0
        
        Timber.e("processStatusFetch:Execute")
        val statusImages = arrayListOf<File>()
        val statusVideos = arrayListOf<File>()

        val png = MimeTypeMap.getSingleton().getMimeTypeFromExtension("png")
        val jpg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
        val jpeg = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpeg")

        val args = arrayOf(png, jpg, jpeg)
        val where = (MediaStore.Files.FileColumns.MIME_TYPE + "=?"
                + " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?"
                + " OR " + MediaStore.Files.FileColumns.MIME_TYPE + "=?")
        var fileCursorExternal: Cursor? = null
        val orderBy = MediaStore.Files.FileColumns.DATE_MODIFIED
        val column = arrayOf(
            MediaStore.Files.FileColumns.DISPLAY_NAME,
            MediaStore.Files.FileColumns.MIME_TYPE,
            MediaStore.Files.FileColumns.DATE_MODIFIED
        )
        //Please note args,where, orderby are not working with contentResolver.query so i have handled file filter using loop

        val selectionMimeType = MediaStore.Files.FileColumns.MIME_TYPE + "=?"
        val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension("jpg")
        val selectionArgsPdf = arrayOf(mimeType)
        fileCursorExternal = contentResolver.query(
            childrenUriStatuses!!,
            column,
            selectionMimeType,
            selectionArgsPdf,
            "date_modified DESC"
        )
        GlobalScope.launch(Dispatchers.Main + exceptionHandler) {

            async(Dispatchers.IO + exceptionHandler) {
                Timber.e("processStatusFetch:InProgress")
                while (fileCursorExternal!!.moveToNext()) {
                    val nameIndex =
                        fileCursorExternal.getColumnIndex(MediaStore.Files.FileColumns.DISPLAY_NAME)

                    val displayName = fileCursorExternal.getString(nameIndex)
                    val path =
                        "${Constants.whatsApp_root_path}/${Constants.FOLDER_NAME_STATUSES}/$displayName"
                    @Suppress("DEPRECATION")

                    if (getFileType(displayName) == FILETYPE.IMAGE) {
                        statusImages.add(
                            File(
                                Environment.getExternalStorageDirectory(),
                                path
                            )
                        )
                    } else if (getFileType(displayName) == FILETYPE.VIDEO) {
                        statusVideos.add(
                            File(
                                Environment.getExternalStorageDirectory(),
                                path
                            )
                        )
                    }
                }
            }.await()
            Timber.e("processStatusFetch:Done")
            sharedViewModel.statusImages.postValue(statusImages)
            sharedViewModel.statusVideos.postValue(statusVideos)
            completed()
        }


    }
}

到目前为止完成的工作:

使用上面的代码,我已经成功地显示了用于使用文件夹的媒体屏幕。

我还可以成功获取.Statuses文件夹中的所有文件

问题:

当我将图像列表设置为RecyclerviewRecyclerViewAdapter。在将文件设置为Imageview时,我总是收到Permissions Denied异常。

方法1:尝试使用Glide将文件设置为图像视图

Glide.with(itemView.context).load(file.absoluteFile)
    .listener(object :
        RequestListener<Drawable> {
        override fun onLoadFailed(
            e: GlideException?,
            model: Any?,
            target: Target<Drawable>?,
            isFirstResource: Boolean
        ): Boolean {
                        return false
        }

        override fun onResourceReady(
            resource: Drawable?,
            model: Any?,
            target: Target<Drawable>?,
            dataSource: DataSource?,
            isFirstResource: Boolean
        ): Boolean {
            val abc = "yes"

            return false
        }
    }
    ).into(imgview)

发生以下异常

com.bumptech.glide.load.engine.GlideException: Failed to load resource
There were 3 root causes:
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(/storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied))
java.io.FileNotFoundException(open failed: EACCES (Permission denied))
 call GlideException#logRootCauses(String) for more detail

方法2:尝试使用URI将文件设置为图像视图

imgview.setImageURI(Uri.fromFile(file))

发生以下异常

resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/25340b4ddcf44eb2a8d6a5d0509feee9.jpg
W/ImageView: Unable to open content: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg
    java.io.FileNotFoundException: /storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg: open failed: EACCES (Permission denied)
        at libcore.io.IoBridge.open(IoBridge.java:492)
        at java.io.FileInputStream.<init>(FileInputStream.java:160)
        at java.io.FileInputStream.<init>(FileInputStream.java:115)
        at android.content.ContentResolver.openInputStream(ContentResolver.java:1498)
        at android.graphics.ImageDecoder$ContentResolverSource.createImageDecoder(ImageDecoder.java:286)
        at android.graphics.ImageDecoder.decodeDrawableImpl(ImageDecoder.java:1758)
        at android.graphics.ImageDecoder.decodeDrawable(ImageDecoder.java:1751)
        at android.widget.ImageView.getDrawableFromUri(ImageView.java:1011)
        at android.widget.ImageView.resolveUri(ImageView.java:980)
        at android.widget.ImageView.setImageURI(ImageView.java:557)
        at androidx.appcompat.widget.AppCompatImageView.setImageURI(AppCompatImageView.java:120)
        at mypkgname.StatusImageAdapterNew$ImageViewHolder.bindItems(StatusImageAdapterNew.kt:135)
        at mypkgname.StatusImageAdapterNew.onBindViewHolder(StatusImageAdapterNew.kt:81)
        at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
        at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6012)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6279)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:561)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:170)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at androidx.swiperefreshlayout.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:625)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at androidx.viewpager.widget.ViewPager.onLayout(ViewPager.java:1775)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1873)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at androidx.drawerlayout.widget.DrawerLayout.onLayout(DrawerLayout.java:1263)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1829)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1673)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1582)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:332)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:270)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:797)
        at android.view.View.layout(View.java:23242)
        at android.view.ViewGroup.layout(ViewGroup.java:6513)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:3694)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3152)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2123)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8601)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
        at android.view.Choreographer.doCallbacks(Choreographer.java:858)
        at android.view.Choreographer.doFrame(Choreographer.java:789)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1020)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:236)
        at android.app.ActivityThread.main(ActivityThread.java:8051)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:620)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1011)
     Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:166)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7923)
        at libcore.io.IoBridge.open(IoBridge.java:478)
        ... 85 more
    resolveUri failed on bad bitmap uri: file:///storage/emulated/0/Android/media/com.whatsapp/WhatsApp/Media/.Statuses/6a8cb5d8dc8b4aba832c984c4f1e06c4.jpg

阅读这两个异常,我可以理解我为Android内部的Media文件夹授予的权限可能不正确,但当我检查它时,它返回True。 请注意,这仅在Android 11上发生,因为我将SDK版本更新为30。

我无法解决此问题。我认为我需要更多地了解存储访问框架才能正确设置这些权限,因为之后我需要写入/复制文件。

推荐答案

读完这两个例外,我可以理解我为Android内部的Media文件夹授予的权限可能不正确

您不能授予任何东西。相反,您被授予了访问Whatsapp媒体文件夹的权限。但仅在使用SAF时使用。

现在您有一堆不相关的代码,可以在其中定义提供程序和路径。不太好。可怕的代码。

虽然您可能成功地列出了所有文件,但您并没有使用所有这些文件的URI(您应该将它们放在<Uri>列表中以供循环视图使用),但是再次使用各种技巧来构建文件路径。

因此丢弃所有这些提供程序和路径定义。

只需使用直接查询()列出Media文件夹的内容。如果您看到列出了";.Status";文件夹,则转到并列出该文件夹。然后..。将获取的文件保存到URI列表。

您从SAF开始,然后转换为文件。别做这种下流的事。您的SAF权限为无文件权限。您不需要清单中的任何权限即可使用SAF。

这篇关于存储访问框架-权限被拒绝问题,即使授予权限也是如此的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆