Reputation: 96
Not sure if what I'm trying to do is possible or not. I have a "proxy" document provider meaning a document provider that exports aliases for other content using the SAF. I have a dialog fragment that allows a user to set up the alias by presenting an OPEN_DOCUMENT_TREE intent, capturing the URI, granting persistable permissions on that URI, and then passing that URI to the document provider to present that content.
The dialog fragment is able to read/write on the URI it receives from the intent but the provider portion is not able to. The error I always get is:
java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri
I've read many related questions and corresponding replies that folks have posted on working with these persistable READ_URI_PERMISSIONS and have tried all variations suggested in those posts. But I am unable to get this to work. I'm beginning to think that the persistable URI permissions are not available within a provider. Is that the case? Do I need to have the provider invoke an intent so that I can grant these permissions? Perhaps have the provider call into the main activity to access the content. I'd certainly rather access the content directly from the provider. I haven't found anywhere that states once persistable permissions are granted, who are they granted to? Is a provider portion of an app a separate process and is that the issue?
Let me provide relevant code snippets:
The 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>
The dialog fragment that obtains the URI is within the main activity and the code that uses the URI is within the provider.
The dialog fragment of the main activity invokes the OPEN_DOCUMENT_TREE intent using:
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);
The "onCreateResultLauncher" class with the aswsociatd onActivityResult callback is:
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
}
}
}
});
Within the dialog fragment, I test the URI to see if I can read it:
DocumentFile file = DocumentFile.fromTreeUri(getContext(), uri);
if (file.canRead())
System.out.println ("Can Read");
We always can read. Now the provider tries to access the URI as follows:
@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");
The provider does not have read access. So when the provider eventually does a query on the URI, it will fail with the SecurityException shown above.
The questions are:
Thank you for reading through the post.
Upvotes: 3
Views: 1465
Reputation: 96
I've been able to figure this out. I had to change a few things but the main issue I had was with how I built the URI's that I passed around. Here's some of the most relevant change:
The app reads in a URI which wants to be exposed in a custom document provider that is part of the app. When launching the
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);
and then launch the intent. After the user selects a directory to export to the custom document provider, the app will recieve the result callback:
Uri uri = data.getData() ;
ContentResolver cr = getContext().getContentResolver();
cr.takePersistableUriPermission
(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
The URI is then passed to the document provider which creates a root for it. The document provider does not need to explicitly take any permissions. It just needs to make sure to build a correct URI. For example:
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);
It all works as I had hoped. It just needed to smash my head on the desk a few times.
Upvotes: 2