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

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

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.

Markdown format allowed

Submitted comments will be subject to moderation before being displayed.