I am trying to port my app to use the new api DocumentFile. I have implemented calling the new intent ACTION_OPEN_DOCUMENT_TREE. Then the code
Uri treeUri = resultData.getData();
DocumentFile folder = DocumentFile.fromTreeUri(context, treeUri);
folder.listFiles[0].delete(); // THIS IS NOT DELETING the correct directory.
How is it failing? I select a folder in the sdcard, e.g. /storage/sdcard1/temp. I have checked that folder.listFile[0] returns something like /storage/sdcard1/temp/test, BUT the folder that is deleted is /storage/sdcard1/temp when I would expect that it was /storage/sdcard1/temp/test.
What is going wrong?
Regards,
Francis.
Related
I have a simple file inside either the apps getExternalFilesDir or a user selected folder. When i create a subfolder and try to move the file from the parent folder into that newly created subfolder moveDocument() fails.
Logcat says
W/DocumentsContract: Failed to move document
java.lang.IllegalArgumentException: Unknown authority
at android.content.ContentResolver.call(ContentResolver.java:2412)
at android.provider.DocumentsContract.moveDocument(DocumentsContract.java:1520)
Both DocumentFiles give an empty string when i try .getUri().getAuthority()
// file and subfolder are under the same parent
DocumentsContract.moveDocument(context.getContentResolver(),
file.getUri(),
subfolder.getParentFile().getUri(),
subfolder.getUri());
Both DocumentFiles exist, i even create files inside that subfolder and that works fine, but i need to move this one from the parent into the sub.
edit:
// if user selected
DocumentFile dir = DocumentFile.fromTreeUri(context, persistedUri);
// if 'internal'
DocumentFile dir = DocumentFile.fromFile(getContext().getExternalFilesDir(null));
DocumentFile subfolder = dir.createDirectory(name);
DocumentFile file = dir.createFile("video/mp4", vidname);
// Uris internal
file:///storage/emulated/0/Android/data/com.foo.bar/files/
file:///storage/emulated/0/Android/data/com.foo.bar/files/vid
file:///storage/emulated/0/Android/data/com.foo.bar/files/1656602728866.mp4
All Uri values passed to moveDocument() have to be "document" Uri values, either obtained directly from the Storage Access Framework or derived from other Uri values that were (e.g., a particular document in a tree obtained by ACTION_OPEN_DOCUMENT_TREE / ActivityResultContracts.OpenDocumentTree). In particular, moveDocument() only works within a single provider, and only if FLAG_SUPPORTS_MOVE is included.
file: Uri values, whether created directly or via DocumentFile, are ineligible.
In my app, I did code for selecting a directory with persistence permission using android ACTION_OPEN_DOCUMENT_TREE. I did everything successfully, but the problem is I can not create a file inside the sub-directory and cannot get a list of files from the sub-directory. It gives me errors like Permission Denial: writing com.android.externalstorage.ExternalStorageProvider uri
but according to android official doc, it says Your app can then access any file in the selected directory and any of its sub-directories.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
activity.startActivityForResult(intent, PERMISSION_CODE);
in onActivityResult
if (requestCode == PERMISSION_CODE) {
if (resultData != null) {
Uri treeUri = resultData.getData();
final int takeFlags = resultData.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
if(DocumentFile.fromTreeUri(context, treeUri).findFile("backup") == null){
Uri backupDirUri = DocumentFile.fromTreeUri(context, treeUri)
.createDirectory("backup").getUri();
//this statement gives me error
DocumentFile.fromTreeUri(context, backupDirUri)
.createFile("text/plain", "34234234.txt")
.getUri();
}
}
}
Suppose an app user select a directory inside SDcard named with MyFolder then I have created a directory backup inside MyFolder but I can not create a file inside the backup directory using the backup directory URI.
For some reason using getUri() on a DocumentFile instance and then attempt to create another DocumentFile from that Uri fails.
Solution: don't attempt to operate on a Uri. Work with the DocumentFile instance.
The first call to fromTreeUri must be using an Uri or Uri from String. But from that point create new files by using the returned DocumentFile.
For example, you can try this:
if (requestCode == PERMISSION_CODE) {
if (resultData != null) {
Uri treeUri = resultData.getData();
final int takeFlags = resultData.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
DocumentFile root = DocumentFile.fromTreeUri(context, treeUri);
if (root.findFile("backup") == null) {
DocumentFile backupDirUri = root.createDirectory("backup");
// this statement should not give error now
DocumentFile f = backupDirUri.createFile("text/plain", "34234234.txt");
// Write to your just created file.
// openOutputStream with f.getUri() will work at this point.
// For this example, plain OutputStream to write bytes,
// wrap it with writers or something more functional.
OutputStream os = context.getContentResolver().openOutputStream(f.getUri());
os.write("It works!".getBytes(Charset.forName("US-ASCII"));
os.flush();
os.close();
}
}
}
It's not only that going back and forward from DocumetFile to Uri then to DocumentFile again is very inefficient if done in the same function call, but that it always fails.
Why it fails?
Well... sorry, I don't know. I just know that in my test devices it fails. I don't know why.
You can safely going from Uri to DocumentFile in future iterations, or with files that already exists in the file system (I mean, not just created during that same function call).
You cannot go from Uri to DocumentFile if you just created a file during that same function call. But you can safely operate on the returned DocumentFile of the just created file. That's why you must remember the returned DocumentFile, not its Uri, if you plan to immediately operate on that just created file.
If you just want to operate on the file at a later time, then you can save the Uri. And you probably want to remember it, because I'm not sure of what happens to a DocumentFile instance once the app is suspended then resumed. In that case, Uri may be safer. Also the string representation of a Uri is what you can save to preferences or any kind of data base/custom file.
But again, if you want to immediately do something with the created file, don't try to get its Uri and then create a DocumentFile. Just use the DocumentFile you already got.
To me, it looks like DocumentFile.createDirectory and DocumentFile.createFile returns before the device file system is updated. This is a guess. I still has to hear the true explanation.
That would explain why you can operate with the returned DocumentFile instance, of a just created child file/directory, but if you save the Uri and try to call any DocumentFile function that accepts an Uri, with the Uri of a just created file/directory, then it fails.
Another possibility is that you cannot create a second DocumentFile while another refers to the same uri. Remember that you may not have references to it, but it exists until garbage collection.
I'm trying to adapt a File-based document system to something using DocumentFile in order to allow external storage read/write access on API >= 29.
I get the user to select the SD card root using Intent.ACTION_OPEN_DOCUMENT_TREE, and I get back a Uri as expected, which I can then handle using:
getContentResolver().takePersistableUriPermission(resultData.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
I can browse successfully through the external storage contents up to the selected root. All good.
But what I need to be able to do is write an arbitrary file in the chosen (sub)folder, and that's where I'm running into problems.
DocumentFile file = DocumentFile.fromSingleUri(mContext, Uri.parse(toPath));
Uri uri = file.getUri();
FileOutputStream output = mContext.getContentResolver().openOutputStream(uri);
Except on the openOutputStream() call I get:
java.io.FileNotFoundException: Failed to open for writing: java.io.FileNotFoundException: open failed: EISDIR (Is a directory)
That's slightly confusing to me, but the "file not found" part suggests I might need to create the blank output file first, so I try that, like:
DocumentFile file = DocumentFile.fromSingleUri(mContext, Uri.parse(toPath));
Uri uri = file.getUri();
if (file == null) {
return false;
}
if (file.exists()) {
file.delete();
}
DocumentFile.fromTreeUri(mContext, Uri.parse(getParentPath(toPath))).createFile("", uri.getLastPathSegment());
FileOutputStream output = mContext.getContentResolver().openOutputStream(uri);
I get a java.io.IOException:
java.lang.IllegalStateException: Failed to touch /mnt/media_rw/0B07-1910/Testing.tmp: java.io.IOException: Read-only file system
at android.os.Parcel.createException(Parcel.java:2079)
at android.os.Parcel.readException(Parcel.java:2039)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:188)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:658)
at android.content.ContentResolver.call(ContentResolver.java:2042)
at android.provider.DocumentsContract.createDocument(DocumentsContract.java:1327)
at androidx.documentfile.provider.TreeDocumentFile.createFile(TreeDocumentFile.java:53)
at androidx.documentfile.provider.TreeDocumentFile.createFile(TreeDocumentFile.java:45)
Which doesn't make sense to me, since the tree should be writeable.
For what it's worth, the Uri I get back from Intent.ACTION_OPEN_DOCUMENT_TREE looks like this:
content://com.android.externalstorage.documents/tree/0B07-1910%3A
Interestingly, when I use that Uri to create a DocumentFile object to browse, using documentFile = DocumentFile.fromTreeUri(context, uri), then documentFile.getURI().toString() looks like:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3A
i.e., it's had something appended to the end of it.
Then, I descend into what should be a writeable folder (like "Download"), and try creating a writeable file as described above. The "Download" folder gets the Uri:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3ADownload
and the Uri I'm using for toPath, above, is then:
content://com.android.externalstorage.documents/tree/0B07-1910%3A/document/0B07-1910%3ADownload/Testing.tmp
which leads to the problems described previously trying to create it.
I haven't actually found any decent information about writing an arbitrary file under Storage Access Framework restrictions.
What am I doing wrong? Thanks. :)
Uri uri = uri obtained from ACTION_OPEN_DOCUMENT_TREE
String folderName = "questions.59189631";
DocumentFile documentDir = DocumentFile.fromTreeUri(context, uri);
DocumentFile folder = documentDir.createDirectory(folderName);
return folder.getUri();
Use createFile() for a writable file.
I'm [still] new on android development and about Java and about Kotlin (also an explanation in Java could be ok, however, I'm studying it also, Kotlin is prefered) and I'm struggling for deleting a simple downloaded file into the ExternalStorage.
Of course I enabled permission for read & write, and, even if this code returns a "True", I still can see the untouched file into my Download folder
here the code:
___UPDATE
// uri of my file in external storage ~/Download dir
var uri = Uri.parse (Environment.getExternalStorageDirectory().getPath() + "/Download/$myFilename$myExtensionVar")
// file object pointing at uri of file in external storage
val downloadedFile = File(uri.toString())
var deletedBool:Boolean = downloadedFile.delete()
println("myTag - deleted Boolean: $deletedBool")
if (deletedBool){
println("myTag - uri of file-to-be-deleted: $uri")
var secondStepToDelete:Int = context.getContentResolver().delete(uri, null, null)
println("myTag - second Step for deletion: $secondStepToDelete")
}
The file i am trying to rid of is a multimedia file (.mp3) and I added the second block of code (the one inside the IF statement) since I found that should work, having to do with the "DocumentProvider" (I'm new and I still don't know how to proper call its methods..) but, of course, It doesn't work at all.
I think I do need the ID (long type i guess) for the file stored into the external storage, however I haven't found yet how to get it
Thanks in advance for the help!
To build a File object, use the File constructor. To build a File object for a location off of a certain root directory, use the two-parameter File(File, String) constructor:
val downloadedFile = File(Environment.getExternalStorageDirectory(), "Download/$myFilename$myExtensionVar")
Unless you are getting a Uri from DownloadManager or something, there is no Uri that you need to delete().
I have more written here on external storage and how to work with it.
In Kotlin
file.deleteRecursively()
I've followed this How to use the new SD card access API presented for Android 5.0 (Lollipop)? but still not satisfy my problem.
My application is almost similar to ES Explorer, where it will display all files to edit. When user finish editing the file, it will save the changes through this call OtherPartyImageSaver.save(File f, Metadata updatedMetadata)
I tried the following, but failed:
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
OtherPartyImageSaver.save(new File("/storage/sdcard/Download/hello.jpg"),
updatedMetadata);
This class doesn't work with OutputStream.
Do I have to give up on OtherPartyImageSaver or is there any way to achieve this?
If I have to give up with the library, can Uri from other provider be granted for modification?
Uri mediaUri = Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
"45");
Because the Uri _ID is retrieved by querying database.
Do I have to give up on OtherPartyImageSaver
If it only works with File, and you want to support removable storage, then either you will have to give up on OtherPartyImageSaver, or you will have to modify it to support streams (if the class is open source).