I am testing the Storage Access Framework for my app.
In my app the user selects a folder as an access point to the filesystem for the app itself.
This method is used:
static public void openPickerForFolderSelection(Activity activity, int requestCode)
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); //useful?
activity.startActivityForResult(intent, requestCode);
}
In the onActivityResult method the following code is called:
static public String takePermanentReadWritePermissions(Activity activity, Intent data)
{
int takeFlags = data.getFlags()
&
(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
ContentResolver resolver = activity.getContentResolver();
resolver.takePersistableUriPermission(data.getData(),takeFlags);
return data.getData().toString();
}
Example, the Uri is:
content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Faccesspoint1
In fact, the Uri happens to be authorized as detected by this method:
static boolean arePermissionsGranted(Activity activity, String uriString)
{
ContentResolver resolver = activity.getContentResolver();
List<UriPermission> list = resolver.getPersistedUriPermissions();
for (int i = 0; i < list.size(); i++){
Log.d("compare",uriString+" "+Uri.decode(list.get(i).getUri().toString())+" "+String.valueOf(list.get(i).isWritePermission())+" "+String.valueOf(list.get(i).isReadPermission()));
if(((Uri.decode(list.get(i).getUri().toString())).equals( uriString)) && list.get(i).isWritePermission()&& list.get(i).isReadPermission()){
return true;
}
then, when it is necessary to create a new folder in the selected folder the following method is called but there is an error before attempting to create the folder:
static public boolean createFolderInFolder(Activity activity,String parentFolderUriString,String folderName)
{
boolean result=false;
ContentResolver contentResolver;
Uri parentFolderUri=null;
String id=DocumentsContract.getTreeDocumentId(Uri.parse(parentFolderUriString));
Log.d("id ",id);
parentFolderUri= DocumentsContract.buildTreeDocumentUri(PROVIDER_AUTHORITY,id);
try {
//error here
DocumentsContract.createDocument(contentResolver,parentFolderUri,DocumentsContract.Document.MIME_TYPE_DIR,folderName);
result=true;
} catch (FileNotFoundException e) {
result =false;
}
return result;
}
I get:
java.lang.SecurityException: Permission Denial: opening provider com.google.android.apps.docs.storagebackend.StorageBackendContentProvider from ProcessRecord{1426e0c 13943:com.example.app/u0a439} (pid=13943, uid=10439) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
(note that using
parentFolderUri=Uri.parse(parentFolderUriString);
directly leads to an "invalid Uri" error)
The fact is that the permissions are granted on a different Uri than the one from DocumentsContract.getTreeDocumentId(Uri.parse(parentFolderUriString));
Here's the log:
D/compare: content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Faccesspoint1 content://com.android.providers.downloads.documents/tree/raw:/storage/emulated/0/Download/accesspoint1 true true
Indeed the id value is:
raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Faccesspoint1
that leads to the final Uri value
content://com.google.android.apps.docs.storage/tree/raw%253A%252Fstorage%252Femulated%252F0%252FDownload%252Faccesspoint1
So it seems that I should request permissions also on this different version of the Uri, if I want that no security exception (with permission denial) is issued? But it seems not possible to take the permissions on a different form uri.
How to solve this?
Offtopic: you don't need intent.addFlags()
in 'openPickerForFolderSelection
' method.
Now on your topic - I think you just need to use DocumentContract.buildDocumentUriUsingTree() to get the uri you need (atleast this worked for me when I tried to create file/directory this way), so it would be something like:
Uri oldParentUri = Uri.parse(parentFolderUriString);
String id = DocumentsContract.getTreeDocumentId(oldParentUri );
Uri parentFolderUri= DocumentsContract.buildChildDocumentsUriUsingTree(oldParentUri , id);
On the other hand I suggest you use DocumentFile API - it's much easier imo. your 'createFolderInFolder
' would then look like this:
Uri parentFolderUri = Uri.parse(parentFolderUriString);
DocumentFile parentFolder = DocumentFile.fromTreeUri(context, parentFolderUri);
parentFolder.createDirectory(folderName);
Then you can easily get the created folder uri and do something from there.