Think About READ_EXTERNAL_STORAGE Now

Now that more Jelly Bean devices are cropping up, you really need to think about setting your build target to API Level 16 and adding the READ_EXTERNAL_STORAGE permission to your application if you read (and do not write) from external storage.

Users of Jelly Bean devices can go into Developer Options in the Settings app and check the “Protect USB storage” preference. In theory, only developers will do this. In practice, other people will do so. After all, who wouldn’t want to protect their USB storage? Besides, the confirmation dialog that appears when checking that preference says “some apps may not work until updated by their developers”, putting the blame on lazy developers.

Hence, even before this protection is enabled by default – perhaps as early as the “K” release – a tiny percentage of your users will check this checkbox, and a subset of them will blame you if your app does not work. You will see “EACCES (Permission denied)”-flavored FileNotFoundException entries in your logs when trying to access external storage.

If you hold WRITE_EXTERNAL_STORAGE, you do not need to also hold READ_EXTERNAL_STORAGE – write implies read, in this case.

However:

  • You do not appear to be grandfathered into this permission based on android:targetSdkVersion or android:minSdkVersion, as was the case when WRITE_EXTERNAL_STORAGE was added a long time ago. Hence, all apps reading (but not writing) external storage will need this permission.

  • The 4.1 emulator appears broken, insofar as it does not check the Developer Options preference, and therefore grants access to external storage even if you lack the permission. The R20.0.1 patch release to the tools does not seem to fix this. I have filed an issue about this problem.

  • The problem affects apps that might not realize that they are reading files from external storage, because they are being handed Uri values from an Intent or a ContentProvider query – using a ContentResolver to open a file:// Uri means that you need this permission, as was discussed in an android-developers thread.

Note, though, that the permission needs to be held by the process opening the file, not the one reading it. Hence, a ContentProvider that serves files from external storage via openFile(), and is accessed by consumers via content:// Uri values, will work fine if the ContentProvider has the permission – the consumer does not need it in that case. Hence, if you implement a ContentProvider that returns file:// Uri values, consider switching to returning content:// Uri values that point back to your ContentProvider and openFile(), as that will prevent your content from fouling up other apps that have not yet added this permission.

The biggest issue for some is that you have to switch to a build target of API Level 16 for the toolchain to recognize this permission. Fortunately, the improvements in Lint make it much easier to have your build target set higher than your android:minSdkVersion yet still catch any places where you might have messed up and used too-new APIs without a version guard block in place.