我正在尝试编写一个自定义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()
方法中,我可以通过DocumentsProvider
通过Uri
成功打开我intent.getData()
提供的文件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
总是因
之类的异常而失败android:grantUriPermissions="true"
如果我从选择器UI中的google驱动器或下载提供程序中选择一个文件,则可以通过这种方式获取权限。所以我认为问题出在我的提供者身上。
尽管我指定了UID
?
我怎样才能说服Android为我创建这样的权限授权?
毕竟我不认为我自己可以做到这一点,因为我无法知道打开选择器UI的进程的(\\{[^}]+\\})
,或者至少不知道我知道怎么做。
答案 0 :(得分:1)
修改强>
我之前的回答并不好。出于安全原因,您可以使用“android.permission.MANAGE_DOCUMENTS” 只有系统UI选择器才能列出您的文档。
但在打开文档的应用程序的清单中不需要此权限。
实际上,您不应该获得此许可,因为这是系统许可。
我刚刚对它进行了测试并调用了onPersistableUriPermission表单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.Document 和 DocumentsContract.Root 中的常量。
我不确定是否需要“_id”。
<强> EDIT3:强>
更新了示例代码,以便从/ sdcard打开文档 添加了读/写外部存储权限。
<?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;
}
}
答案 1 :(得分:0)
使用 SAF 时,API 19-25 上的预期行为是为来自您自己的 SecurityException
的 URI 抛出 DocumentProvider
。
这在 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,您可以将布尔值 EXTRA_EXCLUDE_SELF 添加到您的 Intent。
以下是来自官方 Android 开发者博客的说明,证实了这种行为 - https://medium.com/androiddevelopers/building-a-documentsprovider-f7f2fb38e86a