文档提供者的持久 URI 权限

问题描述 投票:0回答:2

不确定我想做的事情是否可能。我有一个“代理”文档提供程序,意思是使用 SAF 导出其他内容的别名的文档提供程序。我有一个对话框片段,允许用户通过呈现 OPEN_DOCUMENT_TREE 意图、捕获 URI、授予该 URI 的持久权限,然后将该 URI 传递给文档提供程序来呈现该内容来设置别名。

对话框片段能够读取/写入从意图接收的 URI,但提供程序部分无法读取/写入。我总是遇到的错误是:

java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri 

我已经阅读了人们发布的有关使用这些持久 READ_URI_PERMISSIONS 的许多相关问题和相应回复,并尝试了这些帖子中建议的所有变体。但我无法让它发挥作用。我开始认为持久 URI 权限在提供程序中不可用。是这样吗?我是否需要让提供者调用意图以便我可以授予这些权限?也许让提供者调用主要活动来访问内容。我当然宁愿直接从提供商处访问内容。我还没有找到任何地方说明一旦授予持久权限,这些权限会授予谁?应用程序的提供程序部分是一个单独的进程吗?这就是问题所在吗?

让我提供相关的代码片段:

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.connectedway.connectedsmb">

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <application
        ...
        <activity ...>
            ...
        </activity>
        <provider ...
           android:permission="android.permission.MANAGE_DOCUMENTS">
        </provider>
    </application>
</manifest>

获取 URI 的对话框片段位于主活动内,使用 URI 的代码位于提供程序内。

主活动的对话框片段使用以下方式调用 OPEN_DOCUMENT_TREE 意图:

    StorageManager sm = (StorageManager)
        getContext().getSystemService(Context.STORAGE_SERVICE);
    StorageVolume sv = sm.getPrimaryStorageVolume();
    Intent intent = sv.createOpenDocumentTreeIntent();
    intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    onCreateResultLauncher.launch(intent);

带有 aswsociatd onActivityResult 回调的“onCreateResultLauncher”类是:

    ActivityResultLauncher<Intent> onCreateResultLauncher =
        registerForActivityResult
        (new ActivityResultContracts.StartActivityForResult(),
         new ActivityResultCallback<ActivityResult>() {
            @Override
                public void onActivityResult(ActivityResult result) {

                if (result.getResultCode() == Activity.RESULT_OK) {
                    Intent data = result.getData();
                    Uri uri = data.getData() ;
                    getActivity().grantUriPermission(
                        getActivity().getPackageName(), uri,                                                      
                        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    final int takeFlags =
                        data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);

                    getContext().getContentResolver().takePersistableUriPermission(
                        uri, takeFlags);

                    // PASS THE URI OFF TO THE PROVIDER
                    }
                }
            }
        });

在对话框片段中,我测试 URI 以查看是否可以读取它:

    DocumentFile file = DocumentFile.fromTreeUri(getContext(), uri);
    if (file.canRead())
        System.out.println ("Can Read");

我们总是可以读书。现在提供者尝试按如下方式访问 URI:

    @Override
        public Cursor queryDocument(String documentId, String[] projection)
        throws FileNotFoundException {

        // convert the documentId passed in to a uri.  The conversion process
        // results in the same URI as that received by the OPEN_DOCUMENT_TREE 
        // intent invoked and tested above.

        DocumentFile file = DocumentFile.fromTreeUri(getContext(), uri);

        if (file.canRead())
            System.out.println ("Can Read");

提供者没有读取权限。因此,当提供程序最终对 URI 进行查询时,它将失败并出现上面所示的 SecurityException。

问题是:

  • 这在架构上受支持吗?
  • 如果是这样,任何人都可以看到上面的片段中有任何明显的错误吗?
  • 如果没有,任何人都可以想出一种方法来解决架构限制吗?

感谢您阅读这篇文章。

android android-fragments android-permissions android-contentprovider securityexception
2个回答
1
投票

我已经弄清楚了。我必须改变一些东西,但我遇到的主要问题是如何构建我传递的 URI。以下是一些最相关的更改:

应用程序读取一个 URI,该 URI 希望在作为应用程序一部分的自定义文档提供程序中公开。启动时

    Intent intent = sv.createOpenDocumentTreeIntent();
    intent.addFlags
        (Intent.FLAG_GRANT_READ_URI_PERMISSION |
         Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
         Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
         Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);

然后启动意图。用户选择导出到自定义文档提供程序的目录后,应用程序将收到结果回调:

    Uri uri = data.getData() ;
    ContentResolver cr = getContext().getContentResolver();
    cr.takePersistableUriPermission
        (uri,
         Intent.FLAG_GRANT_READ_URI_PERMISSION |
         Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

然后将 URI 传递给文档提供者,文档提供者为其创建根。文档提供者不需要明确获取任何权限。它 只需确保构建正确的 URI。例如:

    Uri treeUri = Uri.parse(otgMap.getPath().getPath());
    String externalDocId = // DocId to query
    Uri externalUri = DocumentsContract.buildDocumentUriUsingTree
        (treeUri, externalDocId);

    ContentResolver cr = getContext().getContentResolver();

    Cursor cursor = cr.query(externalUri, projection,
                             null, null, null);

一切都如我所愿。只需要在桌子上撞几下我的头就可以了。


0
投票

Rich:感谢您分享您的答案。我也面临类似的异常,想知道除了解决问题所做的操作之外,您是否还需要添加以下所有标志。

应用程序读取一个 URI,该 URI 希望在作为应用程序一部分的自定义文档提供程序中公开。启动时

Intent intent = sv.createOpenDocumentTreeIntent();
intent.addFlags
    (Intent.FLAG_GRANT_READ_URI_PERMISSION |
     Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
     Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
     Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);

谢谢,

© www.soinside.com 2019 - 2024. All rights reserved.