Get File Uri from Document Id in Storage Access Framework - android

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.

Related

What is the "authority" that DocumentsContract.moveDocument() needs?

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.

Uri Path getting a file list

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.

Getting file size always zero when try from uri

I am getting uri using GET_CONTENT and also it works when used in Glide to show the image. But I am not getting file size. It always shows 0.
I tried using File(uri.path).length()
fun fileSize(uri: Uri){
var a = File(uri.path).length()
}
A Uri is not a file.
Add a dependency for DocumentFile, such as androidx.documentfile:documentfile. Then, replace your code with:
fun fileSize(uri: Uri){
var a = DocumentFile.fromSingleUri(uri).length()
}
The File constructor takes a file system pathname as as its argument. You have given it a "path" extracted from a URI which is probably not a pathname for a file system object. (It might be if you are using a file: URL, but you are not checking the schema, so you can't be sure.)
If your File object has a pathname for a non-existent file, then when you call File::length, the documentation states that length() will return zero.
In the Java world you could try the following:
URI uri = ...
File file = new File(uri);
long length = file.length();
However, this only works if:
The URI is an absolute URI with a file: schema.
The file that the URI denotes exists in the local file system namespace.
(If 1. is not true, you get an exception. If 2. is not true, you get a zero result.)
This should also work on the Android platform ... without adding any extra dependencies.

How to delete a file in /download (ora any other folder) in the android external storage with DocumentProvider through Kotlin?

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

_data column not available at FileProvider uri

I'm using the FileProvider pattern for creating content:// uri to files, with the
FileProvider.getUriForFile(this, "com.myapp.provider", file)
function. I have the manifest, provider_paths and everything set the standard way, It creates an uri like content://com.myapp.provider/external_files/music/mysong.mp3.
My issue is that if I try getting the real file path in another app, it doesn't work as the _data column doesn't exist (to be specific the error in logs is E/CursorWindow: Failed to read row 0, column -1 from a CursorWindow which has 1 rows, 0 columns.). For fetching the real path I'm using the also pretty much standard function
final String column = MediaStore.Files.FileColumns.DATA;
final String[] projection = { column };
try {
cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
If I use a different app for sharing the same file it generates an uri like content://com.otherapp.provider/external_files/music/mysong.mp3, from which I can already retrieve the real file path. Any ideas what do I have to do to make sure that my app properly inserts the given uri to ContentResolver? Manual contentResolver.insert(...) functions are not allowed. I've tried different versions of provider_paths.xml and granting all possible read/write permissions to the given uri, but I could never retrieve the real path.
The uri itself generated by me works fine as I can read the file or play the song, my issue is just that I cannot retrieve the real file path that I need.
Thanks
My issue is that if I try getting the real file path in another app
The other app should not be trying to do this.
For fetching the real path I'm using the also pretty much standard function
That works for very few Uri values.
If I use a different app for sharing the same file it generates an uri like content://com.otherapp.provider/external_files/music/mysong.mp3, from which I can already retrieve the real file path.
That is not guaranteed.
Any ideas what do I have to do to make sure that my app properly inserts the given uri to ContentResolver?
You don't. You fix the client app, which should not be attempting to get a "real file path" from a Uri.
my issue is just that I cannot retrieve the real file path that I need.
Instead, for a Uri with a content scheme:
Step #1: Get a ContentResolver, by calling getContentResolver() on some Context (e.g., an activity)
Step #2: Call openInputStream() on the ContentResolver, passing in your Uri, to get an InputStream on that content
Step #3: Consume the content via that InputStream
If you are using some third-party library that can only work with files, copy the data from that InputStream to some FileOutputStream, then use the resulting file with that library.
This way, no matter where the content is coming from (a file that you could access, a file that you cannot access, a BLOB column in a database, etc.), you will have code that works.
See also:
Getting the Absolute File Path from Content URI for searched images
onActivityResult's intent.getPath() doesn't give me the correct filename
Android - Get real path of a .txt file selected from the file explorer

Categories

Resources