【问题标题】:Trying to takePersistableUriPermission() fails for custom DocumentsProvider via ACTION_OPEN_DOCUMENT尝试通过 ACTION_OPEN_DOCUMENT 对自定义 DocumentsProvider 进行 takePersistableUriPermission() 失败
【发布时间】:2016-03-23 18:23:03
【问题描述】:

我正在尝试编写一个自定义 DocumentsProvider,它允许其他应用对其提供的 Uris 获取持久权限

我有一个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>

我的应用拥有MANAGE_DOCUMENTS 权限集

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

(显然这不是必需的,但添加/删除它也无关紧要)。 然后,当我打开 ACTION_OPEN_DOCUMENT 选择器 UI 时,我可以看到我的提供程序

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() 方法中,我可以通过我从intent.getData() 获得的Uri 成功打开我的DocumentsProvider 提供的文件。

但是,尝试使用

保持读取或写入权限
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

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

如果我从谷歌驱动器或选择器 UI 中的下载提供程序中选择一个文件,以这种方式获取权限。所以我认为问题出在我的提供者身上。

为什么尽管我指定了android:grantUriPermissions="true",但没有创建权限授予?

如何说服 Android 为我创建这样的权限授予?

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

【问题讨论】:

  • AndroidManufest.xml android:targetSdkVersion="23"project.properties target=android-23
  • 它永远不会起作用。您可以在我的问题的最后一个代码块中找到示例 Uri
  • 感谢您的努力。是的,对于来自其他内容提供商的Uris,我可以通过takePersistableUriPermission() 成功获取持久权限。我写这篇文章只是为了强调我认为问题出在我的提供程序上,而不是在我尝试获取权限的代码中。

标签: android permissions android-contentprovider storage-access-framework


【解决方案1】:

编辑:

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

但您在打开文档的应用程序的清单中不需要此权限
其实你不应该能够获得这个权限,因为它是系统权限。

我刚刚测试了一下,调用takePersistableUriPermission表单onActivityResult是成功的。

我将 DocumentProvider 用于模拟数据(一个根,3 个 txt 文档)。
如果它仍然对您不起作用,则可能是您的文档提供者存在问题。

EDIT2:

示例代码

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);
    }
}

注意:
您可以使用 DocumentsContract.DocumentDocumentsContract.Root 中的常量。
我不确定是否需要“_id”。

EDIT3:

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

AndroidManifest.xml

<?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);

onActivityResult

@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;
    }
}

【讨论】:

  • 我不知道这会如何改善这种情况。我特别希望只有通过 ACTION_OPEN_DOCUMENT 启动的系统 UI 选择器才能访问我的提供程序。按照建议删除或更改权限名称并不能解决所描述的问题。错误保持不变。
  • 您是否可以将应用程序用于意图而不是系统 UI 选择器?我认为系统选择器应该返回 uri "content://com.android.providers.media.documents/..." 但您正在打开应用程序的内容提供程序,而不是通过系统提供程序。
  • 我承认我没有测试它,但我很好奇,所以我现在要测试它。
  • “难道您不能将您的应用程序用于 Intent 而不是系统 UI 选择器吗?”是的,这是可能的,但我主要希望可以通过系统 UI 选择器访问提供程序。我使用自己的应用程序主要只是为了在这里测试提供程序。
  • 据我了解,系统权限“android.permission.MANAGE_DOCUMENTS”适用于 UI 选择器。因此使用此权限的文档提供者只允许 UI 选择器获得持久权限。然后您的应用程序从 UI 选择器获取文档,并且 UI 选择器获取该文档的持久权限。当您想在重新启动后重新打开文档时,您将打开 UI 选择器内容提供程序,该内容提供程序会从您的文档提供程序打开文档。你可以在安装你的应用程序后检查 logcat 吗?查找“未授予权限”文本。
【解决方案2】:

使用 SAF 时,API 19-25 上的预期行为是为来自您自己的 DocumentProvider 的 URI 抛出 SecurityException

这在 API 26 及更高版本上有所改变,现在允许 URI 的持久 URI 权限,即使来自您自己的进程(没有官方文档,但通过测试观察)

但是,即使您在尝试获取持久 URI 权限时获得了 SecurityException,您仍然可以始终访问从您自己的 DocumentsProvider 公开的 URI。

因此,当内容权限来自您自己的进程时,捕获并忽略 SecurityException 是个好主意。

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

这是来自官方 Android 开发者博客的注释,证实了这种行为 - https://medium.com/androiddevelopers/building-a-documentsprovider-f7f2fb38e86a

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-20
    • 2011-03-23
    • 1970-01-01
    • 2019-04-17
    • 2014-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多