Android file storage

Many Android smart phones and tablets have external storage i.e. SD cards, in addition to the internal storage (which all of them have). Android supported external storage until version 4.0. For versions above that, it is up to manufacturer to provide support for external storage. For example, Samsung Galaxy S4 or the latest Sony devices don't have native SD card support. Samsung did bring it back to life in the latest updates, but Cyanogenmod which is installed on my Samsung has yet to implement this.

What do I mean by native SD card support? If the manufacturer does not format the SD card exactly as they should Android does not "see" it. Your apps are then only aware of the internal storage.

Why this would this be a problem? Internal storage, in most cases, is smaller than external storage. If you need a lot of storage you may run into problems. I had problems with Google Translate where you can download offline languages, but my phone did not have native SD card support so I could store the data only to internal 5GB storage.

The app we developed also needed a lot of storage, so we decided that our app had to use external storage, even if Android could not see it. To achieve that I would suggest you to read this basics about storing data in Android Android Storage (if you didn't already).

Here is the code that I wrote to get all the available storage for a device:

public class FileStorage {

    public static String getInternalPath(Context context){
        return context.getFilesDir().getAbsolutePath();
    }

    public static long getInternalFreeSpace(Context context){
        File file = new             
            File(context.getFilesDir().getPath());
        return file.getFreeSpace();
    }

    public static String getExternalPath(Context context){
       File file = new 
            File(context.getExternalFilesDir(null).getPath());

        //check if internal and external is the same stored drive
        if (file.getFreeSpace() ==         
        getInternalFreeSpace(context))
            return null;

        return file.getAbsolutePath();
    }

    public static long getExternalFreeSpace(Context context){
        File file = new File(Environment.getExternalStorageDirectory().getPath());

        //check if internal and external is the same stored drive
        if (file.getFreeSpace() ==                                
        getInternalFreeSpace(context))
            return 0;

        return file.getFreeSpace();
    }

    public static String getAdditionalPath(Context context){
        File storage = new File("/storage/");
        if (!storage.exists())
            return null;

        for (File inFile : storage.listFiles()) {
            if (inFile.isDirectory() && !inFile.getName().contains("emulated")){ 
                long freeSpace = inFile.getFreeSpace();

                if (freeSpace != 0 && freeSpace !=
                    getInternalFreeSpace(context) && freeSpace !=
                    getExternalFreeSpace(context)){

                    File dir = new File(inFile.getAbsolutePath() + "/Android/data/");
                    if (!dir.exists()){//check for android data dir
                        dir = new File(inFile.getAbsolutePath() + "/Katalogi/");

                        if (!dir.exists())
                            dir.mkdir();

                        return inFile.getAbsolutePath() + "/Katalogi/";
                    }

                    String path = inFile.getAbsolutePath() + "/Android/data/" + context.getPackageName();
                    dir = new File(path);

                    if (!dir.exists())
                        dir.mkdir();

                    return path;
                }
            }
        }
        return null;
    }

    public static long getAdditionalFreeSpace(String path){
        File file = new File(path);
        return file.getFreeSpace();
    }

    public static String getMostFreeSpacePath(Context context){
        long internal = getInternalFreeSpace(context);
        long external = getExternalFreeSpace(context);
        long additional = 0;
        String additionalPath = getAdditionalPath(context);

        if (additionalPath != null){
            additional = getAdditionalFreeSpace(additionalPath);
        }

        long max = Math.max(internal, Math.max(external, additional));

        if (max == internal)
            return getInternalPath(context);
        else if (max == external)
            return getExternalPath(context);
        else
            return additionalPath;
   }
}

This "helper" class gets path and free space for every storage available on device. The most important part of the code is the getAdditionalPath method. It checks /storage folder for mounts (e.g. sdcard0, sdcard1, usbdisk,...) and returns the path (if it exist).

NOTE: Method returns the first valid path (for sd card) which ignores Android storage methods internal(getFilesDir()) and external(getExternalFilesDir()). If you want to take care of USB OTG you need to write additional code. You may notice that I created special folder structure if additional storage exists (e.g. /storage/sdcard1/Android/data/com.example.app/). This is the default android app data structure (in private as well as in public storage) and if you delete your app, your data in additional storage is also deleted.

Another helpful method is getMostFreeSpacePath. It returns path to storage with the most free space, which is the default used by our app.

NOTE: There are so many Android smart phones on the market that it's impossible to test the solution for every device. If you run int any problem and give me feedback, I will update this post with additional code. If you found something missing, don't hesitate to email me at gorazd(at)evizija.si.