Android - get DocumentFile with write access for any file path on sd card (having allready gained sd card permission)
Asked Answered
S

2

8

In my app I gain sd card write permission using the following intent. If the user selects the sd card folder from the system file explorer then I have sd card write access.

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, 42);

After that I am able to modify files in th sd card using the DocumentFile class. But I'm having problem getting a DocumentFile for a random file path.

Document.fromFile(new File(path));
Document.fromSingleUri(Uri.fromFile(new File(path)));

both return a DocumentFile object that returns false on .canWrite(). Even though I allready have sd card permission.

So I wrote the method posted in the end of my question to get a DocumentFile that returns true on .canWrite(). But this is slow...And also feels very wrong! There has to be a better way to do this. I also wrote a method that returns the same string as

String docFileUriString = docFile.getUri().toString(); 

for any file, where docFile is the DocumentFile that is returned by the method below. But

DocumentFile.fromTreeUri(Uri.parse(docFileUriString ));

returns a DocumentFile that points to the root of the sd card instead of the DocumentFile path. Which is just weird. Can someone suggest a more elegant solution?

public static DocumentFile getDocumentFileIfAllowedToWrite(File file, Context con){ 

    List<UriPermission> permissionUris = con.getContentResolver().getPersistedUriPermissions();

    for(UriPermission permissionUri:permissionUris){

        Uri treeUri = permissionUri.getUri();
        DocumentFile rootDocFile = DocumentFile.fromTreeUri(con, treeUri);
        String rootDocFilePath = FileUtil.getFullPathFromTreeUri(treeUri, con);

        if(file.getAbsolutePath().startsWith(rootDocFilePath)){

            ArrayList<String> pathInRootDocParts = new ArrayList<String>();
            while(!rootDocFilePath.equals(file.getAbsolutePath())){
                pathInRootDocParts.add(file.getName());
                file = file.getParentFile();
            } 

            DocumentFile docFile = null;  

            if(pathInRootDocParts.size()==0){ 
                docFile = DocumentFile.fromTreeUri(con, rootDocFile.getUri()); 
            }
            else{
                for(int i=pathInRootDocParts.size()-1;i>=0;i--){
                    if(docFile==null){docFile = rootDocFile.findFile(pathInRootDocParts.get(i));}
                    else{docFile = docFile.findFile(pathInRootDocParts.get(i)); }  
                }
            }
            if(docFile!=null && docFile.canWrite()){ 
                return docFile; 
            }else{
                return null;
            }

        }
    }
    return null; 
}
Sclerodermatous answered 27/3, 2016 at 0:3 Comment(6)
Did you ever find a solution? I find the storage access framework to be pretty difficult to useDeception
No I did not...And using DocumentFile class really slowed down perfomance. I used DocumentsContract class instead to do the operations I wanted. I aggree the SAF is quite bad...Sclerodermatous
Any exemple with your DocumentsContract solution ?Watery
This SAF api is probably the worst implemented and documented api ever on Android. I need to get File from DocumentFile or Uri with correct scheme not with content://com.android.externalstorage.documents/tree/primary:, the one with file:///storage/emulated/0 or storage/emulated/0 but i could not find a way. My scenario is: User chooses a path via SAF UI. Saves image with DocumentFile to the path i get using that Uri and get File for writing EXIF data to image but i can't get a file that correctly points to that file with existing uri with content://Ankle
This code works well and i use it in my app New Playlist Manager. To determine if the source lives on internal memory I simply check sourcefile.toString().contains(Environment.getExternalStorageDirectory(). If it does then simply file.delete(). If on sdcard, I use the routine, get valid documentfile and apply documentfile.delete(). Thank you !!!Implied
@Implied How do you manage to get DocumentFile object from normal file?Yashmak
V
2

Document.fromFile(File) doesn't seem to work properly for this purpose on recent devices

Here is link to the answer I've got the most information about the problem https://mcmap.net/q/87308/-how-to-use-the-new-sd-card-access-api-presented-for-android-5-0-lollipop

The key steps are:

  1. Get the write access to the SD card and make it persistent over the phone reboots

    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {

    ...

    getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

    }

  2. After that you can access files and folders no matter how deeply they are nested, but you have to be aware of the complex URL that you'll have to deal with.

If you're a linux guy like me and used to think that everything is a file and everything is simple. This time it's not. A file on SD card, say /storage/13E5-2604/testDir/test.txt where 13E5-2604 is your SD card root folder will be:

content://com.android.externalstorage.documents/tree/13E5-2604%3A/document/13E5-2604%3A%2FtestDir%2Ftest.txt

in the new reality. Why they did it so complex - is another question...

I don't know an API method that transparently and easy converts a normal linux path to this new reality-path. But after you've done it (%3A is : and %2F is / See https://www.w3schools.com/tags/ref_urlencode.ASP for reference) you can create an instance of DocumentFile easily:

DocumentFile txtFile = DocumentFile.fromSingleUri(context,
Uri.parse("content://com.android.externalstorage.documents/tree/13E5-2604%3A/document/13E5-2604%3A%2FtestDir%2Ftest.txt"));

and write to it.

Valency answered 25/1, 2021 at 20:39 Comment(1)
thankyou for the guidance, got me on the right path. I was able to get it working with simpler url : DocumentFile.fromTreeUri(getContext(), Uri.parse("content://com.android.externalstorage.documents/tree/7A71-1EFC%3Atest%2FT2"));Suppositious
I
1

to respond to @AndiMar (21 Mar), I just check if it exists on internal storage, if it does no need to worry.

       try {
        if (sourcefile.toString().contains(Environment.getExternalStorageDirectory().toString())) {
            if (sourcefile.exists()) {
                sourcefile.delete();
            }
        } else {
            FileUtilities fio = new FileUtilities();
            DocumentFile filetodelete = fio.getDocumentFileIfAllowedToWrite(sourcefile, context);
            if (filetodelete.exists()) {
                filetodelete.delete();
            }
        }

    } catch (Exception e) {
        e.printStackTrace();

    }
Implied answered 25/3, 2018 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.