Akom's Tech Ruminations

Various tech outbursts - code and solutions to practical problems

Switchable Storage Locations in an Android App

Posted by Admin • Saturday, October 13. 2012 • Category: Android

This is also a typical pattern. In my app, I need to offer the user a choice of storage locations without getting too low-level. I am content with offering three options in a ListPreference:
  1. Private to application
  2. Public but on the main storage
  3. Public but on the removable storage (actual SD card)
Here is how I am doing it in a relatively generic and reusable manner (using an enum)

Reusable utility class including the ImageSaveLocation enum

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.content.Context;
import android.util.Log;
import android.os.Environment;

public class MyUtils {

        private static File s_removableStoragePath;

          Utility function that attempts to find the removable storage directory
(as opposed to {@link Environment#getExternalStorageDirectory()} which is usually non-removable)
          by running "mount" and parsing the output.   This implementation looks for
the strings "vfat" and "vold", eg:
          /dev/block/vold/179:97 /mnt/extSdCard vfat rw,dirsync,nosuid,nodev,noexec,noatime,nodiratime,uid=1000,gid=1023,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0

          The result will be cached indefinitely.

          @return either a valid file or null.  This may not be accessible, but it does exist

        public static File findRemovableStorage() {
                if (s_removableStoragePath != null && s_removableStoragePath.exists()) {
                        return s_removableStoragePath;
                s_removableStoragePath = null;
                String result = null;
                try {
                        Process process = new ProcessBuilder().command("mount")


                        InputStream is = process.getInputStream();
                        BufferedReader isr = new BufferedReader(new InputStreamReader(is));

                        String line = null;
                        while ((line = isr.readLine()) != null) {
                                if (-1 != line.indexOf("vold") && -1 != line.indexOf("vfat")) {
                                        String[] blocks = line.split("\\s");
                                        if (blocks.length > 2) {
                                                result = blocks[1];
                        if (result != null) {
                                s_removableStoragePath = new File(result);
                                if (!s_removableStoragePath.exists()) {
                                        s_removableStoragePath = null;
                } catch (Throwable t) {
                                        "Unable to find external mount point");
                return s_removableStoragePath;
        interface ImageSaveLocationLookup {
                File getFileLocation(Context context);
        public enum ImageSaveLocation {
                Private(new ImageSaveLocationLookup() {
                        public File getFileLocation(Context context) {
                                return context.getFilesDir();
                }, R.string.description_file_save_path_private),
                Public(new ImageSaveLocationLookup() {
                        public File getFileLocation(Context context) {
                                return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
                }, R.string.description_file_save_path_private),
                Removable(new ImageSaveLocationLookup() {
                        public File getFileLocation(Context context) {
                                return findRemovableStorage();
                }, R.string.description_file_save_path_private);
                private ImageSaveLocationLookup _lookup;
                private int _descriptionResource;

                private ImageSaveLocation(ImageSaveLocationLookup lookup, int descriptionResource) {
                        _lookup = lookup;
                        _descriptionResource = descriptionResource;
Get the file location for this option
                  @param context
@return possibly null if this location cannot be used on this device.  The returned
                       directory might also be read-only  
                public File getLocation(Context context) {
                        return _lookup.getFileLocation(context);
For UI elements that may offer a description for each enum value
                public int getDescriptionResource() {
                        return _descriptionResource;
Safer alternative to {@link #valueOf(String)}.  
                  If value is not one of the enum constants, returns defaultValue
@return never null
                public static ImageSaveLocation safeValueOf(String value, ImageSaveLocation defaultValue) {
                        ImageSaveLocation result = defaultValue;
                        try {
                                result = valueOf(value);
                        } catch (Throwable t) {}
                        return result;



Here is how this is actually used

From the activity/service:

ImageSaveLocation saveLocation = MyUtils.ImageSaveLocation.safeValueOf(_defaultSharedPreferences.getString(getString(R.string.pref_image_save_path), MyUtils.ImageSaveLocation.Private.name()), ImageSaveLocation.Private);
File directory = saveLocation.getLocation(this); //this is the context

In the Prefence Activity:

protected void onStart() {
                //good time to populate the list preference with the enum values:
                ListPreference storageOptions = (ListPreference)findPreference(getString(R.string.pref_image_save_path));
                String[] options = MyUtils.enumToStringArray(MyUtils.ImageSaveLocation.values());

0 Trackbacks

  1. No Trackbacks


Display comments as (Linear | Threaded)
  1. No comments

Add Comment

You can use [geshi lang=lang_name [,ln={y|n}]][/geshi] tags to embed source code snippets.
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.
Markdown format allowed

Submitted comments will be subject to moderation before being displayed.