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;
}
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:
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);
}
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.
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With