Uri management for android Q - android

In my app the user can select an existing file or just write a plain file path. Later the app uses the path to perform operations on selected file. I'm trying to study a strategy to face how filesystem access is changed in Android Q.
I see two problems:
I can't use anymore file paths or just use File interface but I need to use a Uri and I need persistent access to this Uri.
Uri are not "predicatable", i.e. if the file doesn't exist at the moment the user can't select anything and it can't guess what will be the Uri (right?)
For point one I could use ACTION_OPEN_DOCUMENT_TREE with persistent read/write on the root to have access to all files, but I have no idea how to manage point two. To be noted that the file is not created by my app but it could be created later from another app and my app just use it. How can I manage point 2?

Related

Is it still possible to destructively modify files that you don't own under android 10/11 (api 29/30)?

I'd like to write an app that crops/resizes photos and writes them back to shared storage in their new size but everything I've read about how SAF and mediastore works suggests that at best I'd only be able to write the file back with a new name. And at worst I might not be able to open the files at all if I'm not the original creator. A solution that involves triggering a SAF dialog to open the file is reasonable though I'd like to be able to just batch change all the files (photos) in a folder (say dcim). But if I have to also trigger a SAF dialog to write the file back out that's pretty yucky.
Obviously if I were to target pre-10 versions of android this could still be done, but then I'd be blocked from the playstore, and I don't really want to write apps that only I can use.
everything I've read about how SAF and mediastore works suggests that at best I'd only be able to write the file back with a new name
Use ACTION_OPEN_DOCUMENT (or ActivityResultContracts.OpenDocument). You have full read-write access to the content identified by the Uri that you receive.
A solution that involves triggering a SAF dialog to open the file is reasonable though I'd like to be able to just batch change all the files (photos) in a folder (say dcim)
Use ACTION_OPEN_DOCUMENT_TREE (or ActivityResultContracts.OpenDocumentTree). You have full read-write access to all of the documents inside of that tree.

System.IO write operations not working in Android 11 outside of app sandbox

I have read about and think I understand the essentials of changes in Android 10 and 11. Gone are the days of accessing folders and files outside of the Android app sandbox willy nilly. That's fine. Just need a way forward and that's become difficult.
I have 2+ apps that share a local Sqlite database and related files in a folder. One or more of the apps in the group might be installed - no guarantee on which of the apps are present. On iOS and Windows (UWP) there is a nice "app group" (iOS name for it) style concept that supports this kind of arrangement formally in the platform. First one installed/run will create the local storage files. Last app in the group uninstalled and the OS cleans up the shared storage location. Android never had this concept so a common location was created outside of the app specific sandbox.
After studying the options available going forward, seems like the "Best" option was to use the Storage Access Framework (SAF) to get permission from the user for some common folder to use. Note that although there are many different "sharing" options in Android, none of them are great for this use case, and most are not friendly to cross platform Xamarin C# without wrapping them somehow. This "Best" option using SAF still requires the user to independently pick the SAME folder from each app that wants to share the local db/files. You know users are going to mess that up, but that's beside the point at the moment.
In testing this approach, I have been able to use the SAF picker to get the user to choose a folder. The Documents folder is what I've been choosing to test with as a folder. From there the app attempts to create a subfolder where all this shared "app group" content would go. Unfortunately simply doing a Directory.CreateDirectory(path) gives a System.IO.IOException: 'Read-only file system'. I checked am I am still able to do Directory.CreateDirectory(path) in the app sandbox (GetExternalFilesDir), just not the SAF chosen location.
I am also able to create a directory in the SAF location if I stick to the SAF API, such as illustrated in the Xamarin Android sample here: https://github.com/xamarin/monodroid-samples/blob/master/android5.0/DirectorySelection/DirectorySelectionFragment.cs#L169-L188.
Is there any way to treat the SAF location chosen by the user just like a normal file system and use System.IO operation to manipulate it? The app has been given permission but those ops don't seem to work in that location. Or is there a better overall approach to this problem that I've totally missed?
Normal Java File I/O does not work with Scoped Storage. File paths and File or Directory objects do not worked in Storage Access Framework, you have to do everything through the DocumentFile API. DocumentFile has the ability to create files and directories in locations that the user has granted your app access to through the File-picker dialog.
There IS a way for normal/traditional System.IO file I/O to work after converting the SAF content to a classic file system path. Using the FileUtil logic in this answer https://stackoverflow.com/a/36162691/1735721 I was first able to get permission to a folder from the user:
var intent = new Intent(Intent.ActionOpenDocumentTree);
StartActivityForResult(intent, 1);
The in OnActivityResult(_, _, Intent resultData) use the file util logic:
var folderPath = FileUtil.GetFullPathFromTreeUri(resultData.Data, this);
var filePath = Path.Combine(folderPath, "test.txt");
At that point filePath represents the path and filename in the chosen directory tree, and normal C# System.IO operations are available to the app for that file e.g. StreamWriter and StreamReader.
NOTE: I was creating "test.txt" directly in the chosen folder. This worked to create the file in "A" but then "B" couldn't read that same file (Unauthorized exception). At some point I created a subfolder and "test.txt" was created there instead...then both "A" and "B" could read and write the same file. Unfortunately, a couple days later, I couldn't repeat that. So as it stands this is only a partial solution.

How to edit already present file in SD CARD using SAF(storage access framework)?

I have a doubt I read these tutorials just to get the clear understanding of the SAF introduced in kitkat in higher version of android How to use the new SD card access API presented for Android 5.0 (Lollipop)?
How to persist permission in android API 19 (KitKat)?
Android API below 19 equivalent for ContentResolver takePersistableUriPermission
Android Gallery on KitKat returns different Uri for Intent.ACTION_GET_CONTENT
Now i have a question when the intent is fired and i get select the sd card to get the uritree from the intent which i am getting but is the root tree I will have to iterate through the uri to get the specific file uri.
Now the question is I have mp3 files in my phone and I want to edit that specific file which is selected so how can i get that selected files uri from the tree and edit it?I tried editing directly using file but it makes my mp3 file disappear and i dont want to fire SAF intent again n again so how to check is user has given permission to sd card or not?
P.S. I am using jaudio tagger for editing the tags of mp3 files.
THANK YOU!!
I have got answers of almost everything in this question Now the issue if I am using jaudio tagger library for tagging mp3 files which takes File as a objects but android 4.4 and above wont let u modify File objects so I just want to knw the alternative how can i edit File objects in 4.4 above?
This is how I did it,
first copy the file from the sdcard to the phone memory and then do the desired changes and cut and paste the file again at its original place.
Example is here where I am editing the tag of mp3 file which is in the sdcard:-
https://github.com/reyanshmishra/Rey-MusicPlayer/tree/master/app/src/main/java/com/boom/music/player/TagEditor
The library you refer to is called JaudioTagger, and it does not and most probably will never accept DocumentFile. The Storage Access Framework is Google's invention and far from any standard. And JaudioTagger is written for portability.
However, I finally managed to get JaudioTagger mostly running with SAF, but had to substantially modify it, even to rewrite some functions. Basically I replaced all File and RandomAccessFile with my own classes, and additionally the handling of temporary files and renaming must be rewritten (e.g. the scheme: copy original file, change copy, remove original file, rename copy to original name).
In fact it might be easier to use the native TagLib instead, because that already accepts a special C++ class as input. All I did was to create such C++ class whose basis is a file descriptor derived from ParcelFileDescriptor derived from Uri derived from DocumentFile.
Finally one might come to the conclusion that the Storage Access Framework is somewhat suboptimal.

How to use the new lollipop SD Card Access API to delete and modify files

So I have an app which is a music player.
Many times a user plays a song and wants to delete it. Many times he finds that there duplicates of files and hence wants to delete it.
Many users have more than thousands of songs and it would not be an appropriate solution if the user has to to pin point the location of the file he wants to delete.
I came across this How to use the new SD card access API presented for Android 5.0 (Lollipop)?
and it tells how to create the files given the whole uri.
But The appropriate solution would be if he could just choose the root directory(SD CARD ) instead of the whole path and grant the required permission and the app could manage the rest. This is exactly I want to achieve.
Given that i have only the file path of the file to be deleted and the uri of the root directory from the sd card access framework, what is the best way of deleting the file?
is that even possible ? or each time a user has to delete a file, he will have to pin point the location of that file as well?
The link you mentioned (How to use the new SD-Card access API presented for Lollipop) gives instructions on how to prompt user to choose the root directory using ACTION_OPEN_DOCUMENT_TREE.
After the user chooses the root node then your code will have a DocumentFile that represents the root node. You say you already have the file path. If so then use the segments of the file path and follow the path down the hierarchy using DocumentFile.html#listFiles().
You'll finally have the DocumentFile that represent the file you want to delete, then call DocumentFile.html#delete()

Android: Resolve URI paths when opening a pair of files

I am adding support for importing a specific type of file format from another (Windows) app. This particular format keeps the data in a pair of files. These files use the same filename, with different extensions, eg myfile.ext and myfile.ex2.
The scenario is as follows:
The user selects myfile.ext from dropbox, google drive or other
By knowing the filename/path, I want to resolve myfile.ex2 at the same location and open it
The problem is, that when using the URI provided from the file chooser, the URI's looks like this:
content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D154
There are several solutions here involving getting the real file name using getContentResolver(), and pick the original file name from the returned cursor.
Is there a generic way to obtain an absolute path with the actual file name, change extension and then open it ?
There are several solutions here involving getting the real file name using getContentResolver(), and pick the original file name from the returned cursor.
None of which will work reliably, particularly with the providers that you cited.
Is there a generic way to obtain an absolute path with the actual file name, change extension and then open it ?
No.
First, there is no absolute path, because the files in question do not have to be on the device. After all, "dropbox, google drive or other" are cloud services, at least for many values of "other". There is no requirement that just because a cloud service downloaded one file to the device, that is has to download all files to the device.
Second, there is no absolute path that you can necessarily use, because an intelligently-written "dropbox, google drive or other" will have the files on the app's portion of internal storage, which your app cannot access.
Third, a Uri is an opaque handle, just as a URL is. There is no requirement that the Web sites of "dropbox, google drive or other" provide Web URL relative addressing between two arbitrary files held by their services. Similarly, there is no requirement that the on-device file providers provide Uri relative addressing between two arbitrary files held by their services, or even to use filenames that are recognizable.
The main options that I see for you are:
Have the user pick each file individually.
Have the user combine the files, such as putting them in a ZIP archive.
Have the user pick each file, but in one Storage Access Framework ACTION_OPEN_DOCUMENT operation, via EXTRA_ALLOW_MULTIPLE (warning: only practical if your minSdkVersion is 19 or higher, and you will have to handle the case where the user screws up and does not choose exactly two files).
Have the user organize the files, such as putting them in a single directory/folder, and use the Storage Access Framework's ACTION_OPEN_DOCUMENT_TREE Intent (warning: only practical if your minSdkVersion is 21 or higher).
Switch to using direct Web service APIs for whatever service(s) you wish to integrate with, if they, through their APIs, offer something more amenable to you.

Categories

Resources