The Storage Access Framework: Counterpoints
Last Friday, XDA Developers ran an article entitled “The Storage Access Framework is the only way for apps to work with all your files in Android Q. And it’s terrible.”. As you might expect from that title, the author rails against the Storage Access Framework. The commenters do as well, by and large.
I agree that the Storage Access Framework API has issues, and I agree with some of the arguments posed in that article. Other arguments, though, seem to reflect the fact that the author develops a file manager, and therefore takes a particular view of Android app development that does not reflect app development in general.
So, let me offer some counterpoints.
With Q, Google is introducing (and requiring) “Scoped Storage,” which makes Android work more like an iPhone, where storage is isolated to each app. An app can only access its own files, and if it’s uninstalled, all its files are deleted.
Note the comparison to the iPhone. We’ll come back to that.
SAF has been available since Android 5.0 Lollipop, but developers tend to not use it unless required, as it has a difficult and poorly documented API, a poor user experience, poor performance, and poor reliability (largely in the form of device vendor-specific implementation issues).
The API for getting a Uri
from the Storage Access Framework is fairly trivial.
The API for using the Uri
is disjointed, with a mish-mash of ContentResolver
and DocumentFile
being needed if you want something reasonable. And the documentation
is limited, but that is fairly common. But once you find the right methods
on those two classes, the API is not especially difficult and resembles that
of traditional file I/O.
(hey, Yiğit, if you’re reading this: how about a sexy new StorageX library in the Architecture Components? 😃)
The SAF API has gaps, to be certain, such as:
-
There is no
ACTION_CREATE_DOCUMENT_TREE
analog forACTION_CREATE_DOCUMENT
andACTION_OPEN_DOCUMENT_TREE
, leading to some developers to try hacks that prove to be unreliable -
There is no way to specify that we need a read-write document in
ACTION_OPEN_DOCUMENT
, leading to users getting screwed by Google’s “Audio” documents provider
Performance will be worse than with the filesystem API, but whether it matters for the app will vary — more on this later.
And, ideally, Google would have been doing more from a testing standpoint, such as:
-
Writing a conformance test suite, so
DocumentsProvider
implementations can be easily tested to determine if they comply with expectations; and -
Applying that conformance test suite to any pre-installed
DocumentsProvider
implementations as part of the Compatibility Test Suite
The most obvious user-facing change with SAF is the experience of granting an app access to storage. For an app to get access, it makes a request to the OS, which then displays a directory chooser screen. On this screen the user selects the root of a folder hierarchy in which that app will be able to read and write files.
Few apps need access to the complete contents of an arbitrary directory on external
or removable storage. Many apps do not need access to external or removable storage
at all. Many of those that do can get by with just getExternalFilesDir()
. And
even those that need access to arbitrary content often need just a single file.
For this,
the resulting UI is not significantly different than the “file open” and “file save-as”
dialogs that people have been using in desktop operating systems for decades.
Even the dialog that the author decries here is the same as the “choose directory”
dialog that desktop users encounter from time to time.
File I/O performance takes somewhat of a hit under SAF, but the most outstanding problem lies in file directory operations, where it’s ~25 to 50 times slower than the conventional file access possible in Pie.
I have not run the benchmarks, but that result does not shock me. There are two
rounds of IPC for every SAF API call (your app to the SAF, then the SAF to the
DocumentsProvider
). That is going to add overhead, compared to doing the same
thing with the filesystem.
Few apps will need to do this, though. File managers do, as they are not only working with arbitrary directories on external/removable storage, but arbitrary directories with arbitrary contents. Few apps are file managers or have the storage-access characteristics of a file manager. Plus, this is the sort of thing that the SAF could improve upon over time, such as via more aggressive caching.
An even greater performance issue is that some apps will have to copy files to their local “scoped storage” area before they are able to work with them.
I agree. I have been begging library authors to support streams for years.
Many Android apps take advantage of the amazing number of open-source Java libraries in the developer community, and these libraries commonly require direct filesystem access to work.
The real problem comes from libraries that need random access
to the contents of files. Those are basically SAF-resistant and will require data
copying. However, some libraries
take a File
as a parameter, then turn right around, open a FileInputStream
, and
work with that. Such libraries should be able to accept an InputStream
as an
alternative to a File
, and if they don’t, that could get fixed. For open source
libraries, code contributions may be welcome.
Of course, more of these libraries would already have been adapted had more developers
asked them to adapt rather than using script-kiddie workarounds to try to avoid
using a Uri
correctly. Just sayin’.
Google touts the security and privacy benefits of this change, but technically speaking, there is no improvement.
Recall that the author compared scoped storage to the iPhone. Well, in the eyes of the privacy and security experts that I follow, the iPhone is the gold standard, and Android is a trash fire. People hold fundraisers to get iPhones in the hands of at-risk people who otherwise would only be able to afford an Android device. I hope that privacy and security experts will appreciate Android’s move towards being more iPhone-like in this area.
Being able to control storage access on a more granular basis is a fairly massive
win. Just because App X needs access to one particular file on external storage
does not mean that App X should be granted access to all of external storage.
Yet that is what the classic READ_EXTERNAL_STORAGE
and WRITE_EXTERNAL_STORAGE
permissions offer.
When you grant an app access to the root directory of your storage via SAF, it can read, write, and send any file it wants to its nefarious developer in the exact same fashion it could when you granted an app access to storage in Pie.
Few apps need access to the complete contents of an arbitrary directory on external or removable storage. Hopefully, users will think through whether they want to grant that right when an app asks for directory access.
There is more work that could be done to improve privacy and security here. For example, Android needs a Settings screen where users can review what durable storage access grants are outstanding and be able to revoke them. Users ought to be able to control whether access is durable or not at the point of choosing the directory (or even how long the permission grant should be, with values between “session only” and “forever”). A warning dialog might be warranted if the user grants durable access to a root directory, to help ensure the user understands the ramifications of that choice.
Ideally, in the long term, most users will get the “gimme access to everything forever” dialog once or twice a year. Hopefully everything else will get handled with things that are more tightly scoped in terms of breadth (e.g., individual files) or time (e.g., access for less time than “forever”).
Some power users are going to get irritated, though. I am not going to deny that.
The only “security improvement” comes about because it’s now a more arduous process for a user to do this.
Users have used file dialogs in desktop programs for decades. It is possible that those dialogs are a cause of global warming or tooth decay or something, but I doubt it.
The official stated reason in the Android Q beta documentation is to “give users more control over their files and to limit file clutter.”
That part of the documentation needs to get fixed. The revisions in Q Beta 3 basically eliminated that rationale.
If Google is truly concerned about giving users more control over files and clutter, they should architect a solution that directly addresses that, rather than falsely branding the current Android Q design as such an improvement.
It is an improvement. It may not be the best possible improvement; there will be collateral damage stemming from this particular approach. And, as I have written about extensively, I think the rollout had loads of issues. But, it is an improvement. And while it may sound hyperbolic, I can honestly say that lives might be saved in the long run by this change.