The Death of External Storage: App Installer Bugs

The changes from Q Beta 1 to Q Beta 3 for how “scoped storage” behaves have been stunning. I cannot remember a prior case where the behavior between beta releases has changed quite so much. My guess (hope?) is that this reflects a lot of rapid re-engineering work being done by Google developers, and given where Q Beta 3 is right now I am grateful for that work.

However, whenever there is rapid re-engineering work being done, bugs have a tendency to arise.

Braden Farmer pointed out one odd behavior that highlighted three separate-but-related bugs.

App Installers Are Sandboxed

UPDATE 2019-06-08: This was fixed in Q Beta 4.

In theory, in Q Beta 3, android:allowExternalStorageSandbox="false" says “give me the classic view of external storage, please”. And, in general, it works. However, there is at least one case where the system will override your override: if your app gains the right to install other apps.

Specifically, if an app requests REQUEST_INSTALL_PACKAGES in the manifest, and the user eventually grants that permission, at that point, the app gets a sandboxed view of external storage, regardless of android:allowExternalStorageSandbox="false". One moment you can view all of external storage, and the next moment you can’t.

My guess is that this is semi-intended behavior that is simply undocumented and will remain around. I filed an issue to try to get clarity around this.

App Privilege Downgrades Don’t Always Trigger Process Terminations

When the user revokes a runtime permission (e.g., READ_CONTACTS) that had been previously granted to your app, Android terminates your app’s process. This forces your app to go through the whole “do we have permission?” logic, so you find out about the permission loss and can re-request it from the user if appropriate.

However, when the system overrides your override and forces your app to have a sandboxed view of external storage, your app’s process is not terminated. Literally, one moment you can view all of external storage, and the next moment you can’t, without any warning.

IMHO, this is a bug, and having the sandbox forced upon your app should result in process termination.

isExternalStorageSandboxed() Is Unreliable

For runtime permissions, we have checkSelfPermission() as a way to find out if we hold the permission or not. For sandboxed external storage, we are supposed to use Environment.isExternalStorageSandboxed() to detect whether we have a sandboxed view of external storage or not.

This method works well… except in the case where the system overrides your override. Then, Environment.isExternalStorageSandboxed() will still report false, even though your app was forced into a sandboxed view of external storage.

IMHO, this is a bug: Environment.isExternalStorageSandboxed() needs to be able to take into account all reasons why your process may be forced into the sandbox.

While I hope there will not be any more edge cases like this, I would not be surprised if more show up. In the meantime, we will need to just keep trying “scoped storage” and seeing its impacts. And, if your app has the ability to install other apps, keep an eye on these issues and make adjustments as needed.

Many thanks to Braden for pointing this out!

Learn second-generation Android app development — with Kotlin and the Android Jetpack — through CommonsWare’s Android app development training!