Android KitKat 4.4 folder on sd card

Mark picture Mark · May 7, 2014 · Viewed 10.9k times · Source

We've just fallen foul of the new permissions that apply to writing files to sd cards (external storage) on Android 4.4 (EACCES Permission Denied)

Prior to KitKat we set our writable folder like this:

mfolder = Environment.getExternalStorageDirectory().getPath() + "/appfiles";

However after hours of searching I've come to the conclusion, rightly or wrongly that on 4.4 devices to enable writing of files this needs to be changed to:

mfolder = Environment.getExternalStorageDirectory().getPath() + "/Android/data/com.xyz.abc/appfiles";

So mfolder would be something like: /mnt/sdcard/Android/data/com.xyz.abc/appfiles

Is this correct, do we create a folder like the one above on the sdcard to enable 4.4 devices to write files?

mfolder is a String that we save to shared preferences.

Then we have this code that runs once if API>=19 that changes the mfolder String and then copies all the files from the old folder to the new 'kitkat' folder.

if (android.os.Build.VERSION.SDK_INT>=19){
        if (!mfolder.contains("/Android/data/com.xyz.abc/appfiles")){
            if (prefs.getBoolean("kitkatcheck", false)==false){

                //update mfolder from
                      // /mnt/sdcard/appfiles
                      // to
                      // /mnt/sdcard/Android/data/com.xyz.abc/appfiles
                String prekitkatfolder = mfolder;
                String kitkatfolder = mfolder.replace("/appfiles", "/Android/data/com.xyz.abc/appfiles");
                mfolder = kitkatfolder;
                try {
                    File sd = new File(mfolder);
                    if(!sd.exists() || !sd.isDirectory()) {
                        sd.mkdirs();
                    }
                } catch (Exception e) {
                    Toast.makeText(getBaseContext(), "Error creating Kitkat folder!\n" + e.toString(), Toast.LENGTH_LONG).show();
                    return;
                }
                prefEditor.putString("patternfolder", mfolder);
                prefEditor.putBoolean("kitkatcheck", true);
                prefEditor.commit();

                //copy files and folder from old appfiles folder to new.
                AllFiles.clear();
                listFilesAndFilesSubDirectories(prekitkatfolder);
                if (AllFiles.size()>0){
                    for (File child : AllFiles ) {
                        try {

                            File dest = new File(child.toString().replace(prekitkatfolder, kitkatfolder));


                            try {
                                String filePath = dest.getPath().substring(0, dest.getPath().lastIndexOf(File.separator));
                                File subfolder = new File(filePath);
                                if(!subfolder.exists() || !subfolder.isDirectory()) {
                                    subfolder.mkdirs();
                                }
                            } catch (Exception ex) {
                            }

                            copyFile(child, dest);  

                        } catch (Throwable t) {

                        }
                    }

                }


            }

        }

I then notify the user that their files have been copied to the new folder and that due to the new permissions they would have to manually delete the old prekitkatfolder folder. I guess they will only be able to do this if they have a stock file manager or if they unmounted sd card and place it in a PC, due to the new 4.4 permissions?

Also, for us it appears that these 4.4 permissions are not affecting all our users with Kitkat. Some can still write to the original folder location on their external storage and some get the EACCES (Permission Denied) error. Can anyone throw any light on why this might be, one would think it would apply to all 4.4 devices using external storage?

As we have no actual 4.4 device we are having to test this code using the emulator (API 19) but we do not get the EACCES Permission Denied error. So we released a beta version with code above and have been told that the copied files ended up in internal storage, how can that be?

Any ideas what we're doing wrong, thanks in advance

Answer

Mark picture Mark · Sep 26, 2014

Updated solution.

This sets and also creates the folder in the correct place for KitKat.

mfolder = this.getExternalFilesDir("asubfoldername").getAbsolutePath();

However, this isn't full-proof, if the Android device has both an internal and external secondary storage locations, the above will use the internal one. Not really what we want as we require path to removable sdcard or better still the path to the secondary storagelocation with the most free available space.

File[] possible_kitkat_mounts = getExternalFilesDirs(null);

Note the "s" on the end of getExternalFilesDirs. This creates an array of secondary external storage locations.

for (int x = 0; x < possible_kitkat_mounts.length; x++) {
    //Log.d("test", "possible_kitkat_mounts " + possible_kitkat_mounts[x].toString());
    boolean isgood=false;
    if (possible_kitkat_mounts[x] != null){
        isgood = test_mount(possible_kitkat_mounts[x].toString());  
        if (isgood==true){
            arrMyMounts.add(newymounts(Device_get_device_info(possible_kitkat_mounts[x].toString()), possible_kitkat_mounts[x].toString()));
        }   
    }
}                           

//sort arrMyMounts size so we can use largest
Collections.sort(arrMyMounts, new Comparator<mymounts>(){
    public int compare(mymounts obj1, mymounts obj2){
        return (obj1.avaliablesize > obj2.avaliablesize) ? -1: (obj1.avaliablesize > obj2.avaliablesize) ? 1:0 ;
    }
}); 


if (arrMyMounts.size()>0){
    mfolder = arrMyMounts.get(0).name + "/asubfoldername";
    //Log.d("test", "selected kitkat mount " + kitkatfolder);   
}else{
    //do something else...
}

From the array of possible_kitkat_mounts we check via test_mount to see if we can actually write to the selected location and if successful we add that location to arrMyMounts.

By sorting arrMyMounts we can then get the location with the most available free space.

Hey presto, arrMyMounts.get(0).name is a kitkat secondary storage location with the most free space.