Is simple case of creating multiple files and giving the user an easy access to them is impossible with Stored Access Framework?

from the CommonsWare Community archives

At December 1, 2021, 2:34pm, Koger asked:

Hi everyone, I am new here! :slightly_smiling_face: I have been following CommonsWare posts on Stack Overflow and they helped me in numerous cases, but here’s one I am unable to solve, working on it for over a week. I found a couple of almost satisfying solutions, but none covers my case fully.

It will be a long post, especially because I want to describe the approaches I tested so far, so first a quick sum up of the problem. I want, on Android 11 and newer, to be able to:

  1. Create multiple files in a directory of my or user’s choice and preferably do not bother user too much in the process (like having him create some folders manually etc.). Ideally files would be created without any action from the user.
  2. “Pre-select” one of those files for the user, by storing its uri/path and displaying its name and/or path.
  3. Have file’s location easily accessible (from outside of the app) to the user, by either:
  1. Preferably use Intent with ACTION_OPEN_DOCUMENT_TREE for selecting those files, to use system’s DocumentsProvider, instead of implementing some custom file browser.

For whatever reason I am unable to find a solution fulfilling all above requirements.
Here’s the thing I have tried so far:

First problematic approach - creating files with use of ContentResolver
Actually based on one of CommonsWare’s posts.

It seems to be nice and clean, allowing as to create files in system directories like Documents. Sounds good. What is the problem? This creation method returns Uri in format of: content://media/external/file/123
We can get file’s name from this Uri, but not its location. I hoped at least when selecting a new file user could be first shown location of the currently selected one (this way learning where it is stored) with use of DocumentsContract.EXTRA_INITIAL_URI extra, but unfortunately it is not working for Uri generated this way. So 3) is not satisfied.

Second problematic approach - creating files in getExternalFilesDir() directory

  1. Path to app’s directory is long and difficult to read. Uri’s path for a file (Uri.fromFile(file) ) created there would look like below: /storage/emulated/0/Android/data/
    Even with /storage/emulated/0/ part removed for readability, we are left with long and inconvenient path. Also user has to search for our folder among tens of other apps’ folders.

  2. Of course Uri received in above way is useless when using mentioned DocumentsContract.EXTRA_INITIAL_URI, so we can’t go directly to the folder with currently selected file when trying to select other custom files…

  3. When later user selects a new file, depending on its location, Uri’s path will be like: /document/primary:Android/data/ - for file in the app’s folder
    /document/home:FolderName/customFile.txt - for file in one of system directories (here Documents, so actually /Documents/FolderName/customFile.txt)

Sum up: Problem with finding the custom files by the user.

Third problematic approach - let user choose folder in which files are create, using Intent.ACTION_OPEN_DOCUMENT_TREE
Seems like a good idea, because the Uri we will get will allow creation of multiple files and then Uris to them can be used with DocumentsContract.EXTRA_INITIAL_URI.

Problem: When using Intent with ACTION_OPEN_DOCUMENT_TREE, user is shown scary information that app will have full access to all files currently stored in selected location. It doesn’t help, that by default DocumentsProvider opened with this Intent will display phone’s main directory - it would help, if at first I could direct user to a freshly created folder like /Documents/MyAppConfigurationFiles/, but I see no such option.

Fourth approach - a dirty hack
That’s a solution I was proposed, when posting this problem on different forums.
Generally the first approach seems like a best solution, if not for the unreadable paths retrieved from Uris generated using it. There’s no official way for retrieving paths for those Uris, even if we know they are directing to the files we just created in phone’s storage.
Below code however will provide us with path to a file, even from Uris like content://media/external/file/123 :

ParcelFileDescriptor pfd = contentResolver.openFileDescriptor(mySafDocumentUri);
String displayPath = getFallbackDisplayPath();
try {
displayPath = android.system.Os.readlink("/proc/self/fd/" + pfd.getFd());
} catch (Throwable t) { }

It is just perfect - returns paths like /storage/emulated/0/MyFolder/customFile.txt, which are easily readable to the user! But it also a dirty workaround, one not guaranteed to work on all devices, work in the future etc. Unfortunately it seems like there’s no official way to get the same effect.

Sum up

That’s all I have after days of sitting on the topic - thank you for staying till the end :wink: None of the solutions I have gathered and tested seems like a good one, each has a drawbacks.

Am I missing something obvious here?
Can my goals be met or is my app’s logic simply against Android’s standards and to stick with them I have to either use the ACTION_OPEN_DOCUMENT_TREE Intent (potentially scaring user away and bothering him with extra dialogs etc.), or use getExternalFilesDir and accept the lengthy path to files located there, which user will have to memorize, to find those files?

Did anyone stumble upon similar problems?

At December 2, 2021, 12:05am, mmurphy replied:

That is its location.

For example, is a Uri. AFAIK, it is not a file on any computer on this planet. A content Uri does not map to a file, any more than an https Uri maps to a file.

It does not work on all devices today. How many document providers did you test? For example, did you test Google Drive? How about this one, or this one, or this one, or this one?

I repeat: a content Uri does not map to a file.

Your third option seems to meet your numbered objectives, as closely as anything will. It is also the direction that Google steers developers towards. It also is the one that puts users in control, letting the user decide where on the user’s device (or in the user’s cloud storage, etc.) you should put the user’s content.

At December 2, 2021, 12:23pm, Koger replied:

Hi Mark, thank you for the reply.

I see I forgot to write I am using the EXTRA_LOCAL_ONLY extra for Intent with ACTION_OPEN_DOCUMENT action, so actually all the files used in this scenario are located in phone’s memory and all used Uris should indeed be pointing only to such files.
I understand, that generally one shouldn’t assume given Uri is pointing to a file and I would be really happy if I could just remove Uris’ presence at all from this case, but I need to work with what Android offers me.

Addressing your comments:

What I meant, was a location that usable in any way to the user. I should wrote readable location instead.

I did tests on devices with Google Drive and two other document providers present, including one of those you gave links to. It seems however that usage of EXTRA_LOCAL_ONLY extra filters out all of them, because system is always using the default Files app, not offering a choice of other document provider.

I was afraid that would be the final answer - using ACTION_OPEN_DOCUMENT_TREE really is a bother, both to me, and to the user alike.
Do you maybe happen to know of an approach, that would allow me to choose a folder that’s displayed to the user after using Intent with ACTION_OPEN_DOCUMENT_TREE action? It could be one of the system folders like Documents, but preferably one I just created (like Documents/MyApp) or one already existing (I could use my app’s data folder).

At December 2, 2021, 12:55pm, mmurphy replied:

That much is true.

That part is not. EXTRA_LOCAL_ONLY indicates that the data is local, not that it is a file on the filesystem, let alone one that your app can use. It could be stored in a BLOB column of an encrypted database, for example.

That is a readable location. Similarly, is a readable location. A readable location does not mean a file on a local filesystem.

Document providers are inside of “the default Files app”.

What percentage of your users did you ask?

EXTRA_INITIAL_URI requires a document Uri — you cannot pass in a filesystem path, for example. So, what you are looking for is a way to get to a document Uri, one that perhaps you may not have rights to, for the purposes of populating EXTRA_INITIAL_URI. I do not have a recipe for that, but there might be one out there.

No, because the Files app cannot display that directory.

At December 2, 2021, 12:58pm, Koger replied:

I understand now. With your arguments it is now clear to me, that ACTION_OPEN_DOCUMENT_TREE is the only viable solution currently. Thank you for explaining that to me!