Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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; 
}
like image 606
Anonymous Avatar asked Mar 27 '16 00:03

Anonymous


2 Answers

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://stackoverflow.com/a/26765884/971355

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.

like image 159
ka3ak Avatar answered Oct 02 '22 23:10

ka3ak


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

    }
like image 33
Theo Avatar answered Oct 02 '22 23:10

Theo