Android 通过“打开文档”操作尝试获取自定义文档Provider的PersistableUriPermission()失败

Android 通过“打开文档”操作尝试获取自定义文档Provider的PersistableUriPermission()失败,android,permissions,android-contentprovider,storage-access-framework,Android,Permissions,Android Contentprovider,Storage Access Framework,我正在尝试编写一个自定义的文档Provider,允许其他应用程序对其提供的URI拥有持久权限 我有一个DocumentsProvider,我在我的AndroidManufest.xml中声明如下 <provider android:name="com.cgogolin.myapp.MyContentProvider" android:authorities="com.cgogolin.myapp.MyContentProvider" android:grantUriPer

我正在尝试编写一个自定义的
文档Provider
,允许其他应用程序对其提供的URI拥有持久权限

我有一个
DocumentsProvider
,我在我的
AndroidManufest.xml
中声明如下

<provider
   android:name="com.cgogolin.myapp.MyContentProvider"
   android:authorities="com.cgogolin.myapp.MyContentProvider"
   android:grantUriPermissions="true"
   android:exported="true"
   android:permission="android.permission.MANAGE_DOCUMENTS"
   android:enabled="@bool/atLeastKitKat">
  <intent-filter>
    <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
  </intent-filter>
</provider>
(显然这不是必需的,但添加/删除它也无关紧要)。 然后,当我打开
ACTION\u open\u文档
picker用户界面时,我可以看到我的提供者

Intent openDocumentIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("application/pdf");
openDocumentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(openDocumentIntent, EDIT_REQUEST);
而且,在从我的提供商那里选择了一个文件之后,在我的应用程序的
onActivityResult()
方法中,我可以通过
Uri
成功打开我的
DocumentsProvider
提供的文件,该文件是我从
intent.getData()
获得的

但是,尝试将读或写权限持久化为

getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

总是失败,出现如下异常

No permission grant found for UID 10210 and Uri content://com.cgogolin.myapp.MyContentProvider/document/tshjhczf.pdf
如果我在picker UI中从google drive或下载提供商处选择文件,则以这种方式获取权限是有效的。所以我认为问题出在我的供应商身上

为什么尽管我指定了
android:grantUriPermissions=“true”
,却没有创建权限授予

我怎样才能说服Android为我创建这样的许可授权

毕竟我认为我自己做不到,因为我不知道打开选择器UI的进程的
UID
,或者至少我不知道如何操作。

编辑:

我以前的回答不好。出于安全原因,您应该使用“android.permission.MANAGE_DOCUMENTS”。
只有系统用户界面选择器才能列出您的文档

但是在打开文档的应用程序的清单中,不需要此权限
实际上,您不应该获得此权限,因为它是系统权限

我刚刚对它进行了测试,并在ActivityResult上调用TakePersistableUri许可表单成功

我将DocumentProvider与模拟数据一起使用(一个根目录,3个txt文档)。
如果它仍然不适用于您,您的文档提供商可能会出现一些问题

EDIT2:

示例代码 注:
您可以使用文档contract.Document文档contract.Root中的常量 我不确定是否需要“\u id”

EDIT3:

已更新示例代码以从/SD卡打开文档。
添加了读/写外部存储权限

AndroidManifest.xml onActivityResult
使用SAF时,API 19-25上的预期行为是从您自己的
DocumentProvider
为URI引发
SecurityException

这在API 26和更高版本上发生了变化,它现在允许对URI的持久URI权限,即使是从您自己的流程(没有正式文档,但通过测试进行观察)

但是,即使您在尝试获取持久URI权限时获得了一个
SecurityException
,您仍然可以访问从您自己的
文档提供者中公开的URI

因此,当内容授权来自您自己的流程时,最好捕获并忽略
SecurityException

注意:如果您的应用程序包含DocumentsProvider,并且还保留了从ACTION\u OPEN\u DOCUMENT、ACTION\u OPEN\u DOCUMENT\u TREE或ACTION\u CREATE\u DOCUMENT返回的URI,请注意,您将无法通过takePersistableUriPermission()持久访问您自己的URI-尽管它因SecurityException失败,您将始终可以从自己的应用程序访问URI。如果您想在API 23+设备上隐藏您自己的DocumentsProvider以执行任何这些操作,则可以将布尔值EXTRA_EXCLUDE_SELF添加到您的意图中


这里有一条来自Android开发者官方博客的说明,证实了这种行为-

AndroidManufest.xml
Android:targetSdkVersion=“23”
project.properties
target=Android-23
中,它永远不会起作用。您可以在我问题的最后一个代码块中找到一个示例
Uri
。感谢您的努力。是的,对于来自其他内容提供商的
Uri
s,我可以通过
takePersistableUriPermission()
成功获取持久权限。我写这篇文章只是为了强调,我认为问题出在我的提供者身上,而不是我试图获取权限的代码中。我不认为这能改善这种情况。我特别希望只有通过
ACTION\u OPEN\u DOCUMENT
启动的系统UI选取器才能访问我的提供者。按照建议删除或更改权限名称并不能解决所述问题。错误保持不变。难道你不可能出于目的而使用你的应用程序而不是系统用户界面选择器吗?我认为系统选择器应该返回uri“content://com.android.providers.media.documents/...“但你正在打开应用程序的内容提供商,而不是通过系统提供商。我承认我没有测试它,但我很好奇,所以我现在就要测试它。“你不可能出于目的而使用应用程序,而不是使用系统UI选择器吗?”是的,这是可能的,但我主要希望提供程序可以通过系统UI选择器访问。我在这里使用自己的应用程序主要是为了测试提供商。据我所知,系统权限“android.permission.MANAGE_DOCUMENTS”是为用户界面选择器设计的。因此,使用此权限的文档提供程序只允许UI选择器获取持久权限。然后,您的应用程序从UI选取器获取文档,UI选取器获取文档的持久权限。当您想在重新启动后重新打开文档时,将打开UI选取器内容提供程序,该提供程序将从您的文档提供程序打开文档。安装应用程序后是否可以检查logcat?查找“未授予权限”文本。
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
No permission grant found for UID 10210 and Uri content://com.cgogolin.myapp.MyContentProvider/document/tshjhczf.pdf
package com.example.test;

import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsProvider;

import java.io.FileNotFoundException;

public class MyContentProvider extends DocumentsProvider {

    private final static String[] rootColumns = new String[]{
            "_id", "root_id", "title", "icon"
    };
    private final static String[] docColumns = new String[]{
            "_id", "document_id", "_display_name", "mime_type", "icon"
    };

    MatrixCursor matrixCursor;
    MatrixCursor matrixRootCursor;

    @Override
    public boolean onCreate() {

        matrixRootCursor = new MatrixCursor(rootColumns);
        matrixRootCursor.addRow(new Object[]{1, 1, "TEST", R.mipmap.ic_launcher});

        matrixCursor = new MatrixCursor(docColumns);
        matrixCursor.addRow(new Object[]{1, 1, "a.txt", "text/plain", R.mipmap.ic_launcher});
        matrixCursor.addRow(new Object[]{2, 2, "b.txt", "text/plain", R.mipmap.ic_launcher});
        matrixCursor.addRow(new Object[]{3, 3, "c.txt", "text/plain", R.mipmap.ic_launcher});

        return true;
    }

    @Override
    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
        return matrixRootCursor;
    }

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

        return matrixCursor;
    }

    @Override
    public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
                                      String sortOrder)
            throws FileNotFoundException {

        return matrixCursor;
    }

    @Override
    public ParcelFileDescriptor openDocument(String documentId, String mode,
                                             CancellationSignal signal)
            throws FileNotFoundException {

        int id;
        try {
            id = Integer.valueOf(documentId);
        } catch (NumberFormatException e) {
            throw new FileNotFoundException("Incorrect document ID " + documentId);
        }

        String filename = "/sdcard/";

        switch (id) {
            case 1:
                filename += "a.txt";
                break;
            case 2:
                filename += "b.txt";
                break;
            case 3:
                filename += "c.txt";
                break;
            default:
                throw new FileNotFoundException("Unknown document ID " + documentId);
        }

        return ParcelFileDescriptor.open(new File(filename),
                ParcelFileDescriptor.MODE_READ_WRITE);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest
    package="com.example.test"
    xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">

        <provider
            android:name="com.example.test.MyContentProvider"
            android:authorities="com.example.test.document"
            android:enabled="true"
            android:exported="@bool/atLeastKitKat"
            android:grantUriPermissions="true"
            android:permission="android.permission.MANAGE_DOCUMENTS">
            <intent-filter>
                <action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
            </intent-filter>
        </provider>
    </application>

</manifest>
Intent openDocumentIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
openDocumentIntent.addCategory(Intent.CATEGORY_OPENABLE);
openDocumentIntent.setType("text/plain");
openDocumentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(openDocumentIntent, 1);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case 1: // TODO: Use constant
            if (resultCode == RESULT_OK) {
                if (data == null) return; // TODO: Show error
                Uri uri = data.getData();
                if (uri == null) return; // TODO: Show error
                getContentResolver().takePersistableUriPermission(uri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);

                InputStream is = null;
                try {
                    is = getContentResolver().openInputStream(uri);

                    // Just for quick sample (I know what I will read)
                    byte[] buffer = new byte[1024];
                    int read = is.read(buffer);
                    String text = new String(buffer, 0, read);

                    ((TextView) findViewById(R.id.text)).setText(text);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (is != null) try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            break;
    }
}