The Death of External Storage: Correcting a Mistake
UPDATE 2019-06-08: With Q Beta 4 out, the story is now much simpler. I am leaving the original post here for historical reasons.
My post from last Monday had a significant mistake. I wrote:
You cannot write to the standard directories available from
Environment.getExternalStoragePublicDirectory(). So, you cannot write to
DIRECTORY_MUSICor places like that.
That is incorrect — you can write to these locations… sort of.
The reason for my mistake and the reason for the “sort of” qualification help to illustrate what is going on with these external storage changes.
You can write to those directories, but the directories may not already exist for your app… even if they appear for the user.
If you have been working in Android from the early days, you may recall a time
when emulators (and occasionally devices) would not ship with the common external storage
directories already created. So, if you wanted to use
you would need to call
mkdirs() on that
File to ensure that the directory
existed before using it.
However, that missing-directory problem largely cleared up years ago. In my test
app, I foolishly did not bother trying to create the directories. After all,
I could see the directories when browsing the device. So, when I was getting
FileNotFoundException errors, I assumed it was a permissions thing.
Instead, even though I could see the directory, my app could not. Once I updated my app to create the directory, those locations worked as expected… more or less.
So, it appears that each app has its own independent external storage, which Google refers to as the “isolated storage sandbox” in the documentation. Two apps can write the same file to the same path with different contents, because while each app thinks that it is writing to the one true external storage, instead it is writing to its own external storage sandbox, which is independent of the external storage sandbox of other apps.
This explains the problems with the Storage Access Framework and the USB cable. Effectively, the the Storage Access Framework and the USB cable show what I will call “the user’s sandbox”, which is not the same sandbox as that of any particular app.
The primary exception to this are the
Context-supplied locations, like
getExternalCacheDir(). There, the user’s sandbox includes
the per-app sandbox for those directories. While I doubt that
it is implemented this way, you can think of this as a symlink: the user’s
sandbox gets symlinks to each app’s sandbox’s entries in
Android/data/, so the
user can see what the app sees within those areas.
From a privacy and security standpoint, this is a slick solution.
From a usability standpoint, it’s a problem… if Google follows through on
and says that all apps, regardless of
targetSdkVersion, will be subject
to these rules on Android R. Many legacy apps put their files on external storage
outside of the
Context-supplied areas and expect users to be able to get to those
files. Not only does scoped storage not support this, if my analysis is correct,
scoped storage can’t support this without some substantial change to allow the
user to say “show me external storage from the perspective of App X”. Since
we cannot do that even with
adb shell run-as,
it may not be technically possible. Even if it is, from a UX standpoint, it is
unclear how this would be represented to the user (e.g., one USB volume per app?).
UPDATE 2019-04-29: pzychotix pointed out
that there is a way for the user to view these sandboxes: go into
from the user’s external storage root. That provides a list of app-specific directories
by application ID, and those directories are the app’s own external storage roots.
This works, and for developers and other power users is reasonable. I suspect that
the average user will not know about this and may have difficulty identifying the
desired app in the sea of application IDs.
targetSdkVersion solution may be required for usability. In other words,
only apps targeting
Q or higher would get
this behavior, with legacy apps writing directly to the user’s sandbox.
Since the Play Store requires an ever-increasing
targetSdkVersion, most actively-maintained
apps will get to
Q in the not-too-distant future.
Yet the apps that are not being maintained, but still provide value, would still work as
Regardless of the circumstances, I apologize for my mistake. I should have been more careful in my earlier tests.
Need Android app development training for your team? Mark Murphy has trained hundreds! Learn more!