October 15th 2014, Android Storage

October 15th 2014 Photo Gallery | | October 16th 2014 Exiv2

With Android 4.4 Google has restricted the access of external SD cards. What consequences does this have for application developers?

As a matter of fact, the Environment.getExternalStorageDirectory() method does not return the sd card path, as is often mistakingly thought, but the path to the primary external storage, which usually is an emulated internal SD card at /mnt/sdcard using the phone’s internal flash memory. In the terms of the Android API an external SD card is a secondary external storage, like SD cards in a SD card slot. Not to be mixed up with the primary storage the Android API normally works with. To get the terms right, please refer to Dave Smith’s excellent analysis of this.

On Android prior to KitKat, there were various ways to get the secondary storage path of an SD card slot. Here is a selection:

1) One could test the mount points:

Map<String, File> externalLocations = MemoryStorage.getAllStorageLocations();
File sdCard = externalLocations.get(MemoryStorage.SD_CARD);
File externalSdCard = externalLocations.get(MemoryStorage.EXTERNAL_SD_CARD);
Note: The above code requires the MemoryStorage class from http://gist.github.com/loop/5805820, which tries various low level approaches to figure out all mount points of the root file system.

2) One could query the system environment:

String sdCard = System.getenv("EXTERNAL_STORAGE");
String externalSdCard = System.getenv("SECONDARY_STORAGE");
Note: The latter did not work on my LG L7II. It returned an empty path for the external sdcard. Looking at the environment via “adb shell printenv”, it turned out that on my LG the secondary storage is listed in the “EXTERNAL_ADD_STORAGE” environment variable. So to get the secondary storage path we might use the following approach (although there is no guaranty that other manufacturers do not use other non-standard environments):
String externalSdCard = System.getenv("SECONDARY_STORAGE");
if (externalSdCard == "") externalSdCard = System.getenv("EXTERNAL_ADD_STORAGE");

3) As a more generic approach, one could get the root mount point under which all external devices are listed and walk its way through all subdirectories looking for the one with the largest total space (assuming that the external secondary sdcard would be the largest one):

File devroot = Environment.getExternalStorageDirectory().getParentFile();
File[] subdirs = devroot.listFiles();
int maxspc = 0;
int maxidx = -1;
for (int i=0; i<subdirs.size(); i++)
   if (subdirs[i].isDirectory())
      if (subdirs[i].getTotalSpace() > maxspc)
         maxspc = subdirs[i].getTotalSpace();
         maxidx = i;
String externalSdCard = "/storage/sdcard0";
if (maxidx >= 0)
   externalSdCard = subdirs[maxidx].getAbsolutePath();

With KitKat the above approachs might still be working, but they are no longer sufficient, since parts of the external SD card are now protected. But which are the storage parts that are now protected? Android does not appear to provide an API which enforces the discrimination of primary and secondary storage, but it provides a revised API which delivers the paths of private directorys on all storage mount points: getExternalFilesDirs() (as opposed to getExternalFilesDir() prior to KitKat).

The first item returned from these new APIs is always the primary volume. Any additional items are considered secondary. Even though the docs don’t say this very clearly, the CTS (Compatibility Test Suite) enforces it.

The returned paths have no permission restrictions for the owning app, but restricted read and write access for other apps unless they are granted “READ_EXTERNAL_STORAGE” or “WRITE_EXTERNAL_STORAGE” permissions. Any app with those permissions is allowed to access the entire external storage, both on the primary and the secondary side. As opposed to Android versions prior to KitKat, the “WRITE_EXTERNAL_STORAGE” permission is no longer needed for accessing private files on the external storage with KitKat, so it shouldn’t be included by default any longer, unless the app needs access to the entire storage.

Still confused how to create read and write a directory on the external SD card?

Let’s get to the point: Android disallows the access of file contents which other apps have stored on both the primary and secondary storage. Prior to KitKat this was only enforced for the external storage as a whole. The “READ_EXTERNAL_STORAGE” resp. “WRITE_EXTERNAL_STORAGE” permissions were required to access the external storage. There was no native method to get a handle on the secondary storage, one could just use getExternalFilesDir() and the like to get directories on the primary storage. The permission changes on KitKat now mean that now a dedicated storage place for each app is available on both the primary and secondary storage: This primary or secondary storage directory is returned as first or second (or later entry) of getExternalFilesDir(). To access those directories no special permissions are required for the owning app. But those contents are not permanent, since they will be deleted upon app deinstallation. If an app wants to permanently store data on the external storage it has to store it in the usual public directories (like “DCIM” for storing images). To do so, explicit read and write permissions are required. From that point of view, there is no change at all w.r.t. the permissions on KitKat.

October 15th 2014 Photo Gallery | | October 16th 2014 Exiv2