Android 11 / Api 30
Lets say I have a path to a user selected path (URI) on an Android devices, how would I go about walking through that directory and not the sub directories that might be deeper within it, for files with the extension .pref, so that those files could be loaded later.
I've already done code for walking a path with in my own applications directory, but cannot find how I would do that from a user selected path in shared/external storage.
Lets say I have a path to a user selected path (URI)
A Uri is not a filesystem path. For example, https://stackoverflow.com/questions/68510202/uri-path-getting-a-file-list is a Uri.
how would I go about walking through that directory
I am going to assume that you obtained the Uri via ACTION_OPEN_DOCUMENT_TREE / ActivityResultContracts.OpenDocumentTree.
how would I go about walking through that directory and not the sub directories that might be deeper within it, for files with the extension .pref, so that those files could be loaded later
There is no requirement for content in a documents provider to have file extensions, so your approach will not work for all cases. But, you can wrap the Uri in a DocumentFile using fromTreeUri(), then call listFiles() on the DocumentFile:
val docRoot: DocumentFile? = DocumentFile.fromTreeUri(this, uri)
val docPrefs: List<DocumentFile> = docRoot?.listFiles().orEmpty()
.filterNot { it.isDirectory }
.filter { it.name.orEmpty().endsWith(".pref") }
val docs: List<Uri> = docPrefs.map { it.uri }
Here, docPrefs is the list of non-directory children of your root that happen to have a display name that ends in .pref.
so that those files could be loaded later
That will only work if you call takePersistableUriPermission() on the Uri that you got from ACTION_OPEN_DOCUMENT_TREE.
Related
I want the get a list of files in same directory of a given single file from uri e.g [picture #1.1]
content://org.owncloud.documents/document/79
and then select file [picture #1.2]
when I select requested fields
val filePath = arrayOf(MediaStore.Images.Media.DATA, DISPLAY_NAME, RELATIVE_PATH)
val c: Cursor = context.contentResolver.query(uri!!, filePath, null, null, null)!!
c.moveToFirst()
I receive [picture #1.3]
within this directory e.g content://org.owncloud.documents/document/79
Solution 1 :
Query it, but something like https://stackoverflow.com/a/65070765/1079990 doesn't work.
I guess I've grab directory from it and then query all files.
This is the main question !
Solution 2 :
Select the directory directly with eg.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, 123)
But here I can't access ownCloud, how to be able to select this as well ?
First if ACTION_OPEN_DOCUMENT_TREE cannot give you the tree then what you want will be impossible.
Second if you used ACTION_OPEN_DOCUMENT you got an uri for a file. And permission to read and or write it.
Even if you could manage to build up an uri for the directory the file was in (quite possible if you choosed a file from device storage) you would not have permission to read it.
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.
Attempting to navigate folder tree using Storage Access Framework and Uri, I am seeming some unexpected behavior. In short, my test case is a roundtrip DocumentFile -> Uri -> DocumentFile -> Uri test.
(code is for Xamarin but these are underlying Android classes so it should not be language specific.)
void Test(DocumentFile folder)
{
var uri = folder.Uri;
//
// Test case is using an SD card mounted in a card trader in a USB hub.
// uri: {content://com.android.externalstorage.documents/tree/1111-1111%3ADCIM/document/1111-1111%3ADCIM%2F110ND810}
//
var folder2 = DocumentFile.FromTreeUri(context, uri);
var uri2 = folder2.Uri;
//
// uri2: {content://com.android.externalstorage.documents/tree/1111-1111%3ADCIM/document/1111-1111%3ADCIM}
//
// At this point I expected uri2 to be equal to uri, but it's not.
// Instead, uri2 points to the parent of uri
//
}
Question 1: Are my expectations off here? Should I not expect to be able to navigate a SAF folder tree using Uri?
Question 2: If so, what is a suitable workaround?
The following library makes working with SAF so easy. I think you shouldn't get two different uri!
https://github.com/madnik7/PortableStorage
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 am using directory selection as described in this Google Sample. It does provide file name and mime type of the children of the selected directory. I can get Document ID of the file too, if I use COLUMN_DOCUMENT_ID on the Cursor Query.
I am interested in the file URI of the children instead. When I use ACTION_OPEN_DOCUMENT instead of ACTION_OPEN_DOCUMENT_TREE, I get the child uri easily which is just obtained from adding a %2Fchildfile.extention (%2F is just a forward slash). So I tried to get child file uri using the following code -
uri = Uri.parse(docUri.toString()+"%2F"+fileName);
I got the file name, however when I run exists() method on it (By converting it into DocumentFile), it returns false. That means, either I don't have the permission of the file or it's not the correct way to get children uri.
Am I missing something here or is there any other way I can select a folder and get file uri of all of it's children easily.
PS: I am currently checking it in Marshamallow.
After reading the doc and trying out certain examples, I got the following way to get a single file Uri from a selected docUri/treeUri
uri = DocumentsContract.buildDocumentUriUsingTree(docUri,docId);
And then you can convert it anytime into a DocumentFile using following code -
DocumentFile file = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (DocumentsContract.isDocumentUri(context, uri)) {
file = DocumentFile.fromSingleUri(context, uri);
} else {
file = DocumentFile.fromTreeUri(context, uri);
}
}
fromTreeUri() method is required for the selected Tree Directory, so that it can return true on file.exists() method call.
You need to remember that if the children contain any directory, then you can't call childDirectory.listFiles() on it. It'll give UnsupportedOperationException, because you don't have permission to access the child directory's file. Read more about this here.