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")
                                        .redirectErrorStream(true).start();

                        process.waitFor();

                        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) {
                        Log.e("myutils",
                                        "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
                  @return
                 */
                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() {
                super.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());
                storageOptions.setEntries(options);
                storageOptions.setEntryValues(options);
       
        }
 

0 Trackbacks

  1. No Trackbacks

0 Comments

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.