Why is android blocking access to the app specific internal storage folder (and preferences) in release build only?

58 views Asked by At

I have a problem with the release build of my app. The app specific internal storage folder is somehow blocked for reading files (ie. files written by the app are not showing there). My app saves user's projects data in serialised form as text files. These must be read so users can load their projects.

In debug mode it is working normally and everything is as expected. The files are there and can be read. When I set "debuggable=false" for the release build then the data is not available.

I have tried many possible ways of writing/reading from the internal storage. They all behave the same. I have also tried the app specific external storage and internal/external cache folders.

I also tried saving the serialised Strings to Saved Preferences as they do not contain much data. Strangely the reading of saved preferences is also blocked in the release build only. (It looks like same problem as the preferences xml file is stored in internal storage folder.)

I have searched but it seems that nobody else has this problem. I don't understand what is causing it. If android detects a security vulnerablity with my app then I would have thought it would block the whole app and prevent it launching.

My app is not working now, so I will appreciate any suggestions as to the source of the problem please.

Here are the details: The file write function does not throw any IO errors. I have tried various ways to write it. The boolean functions return true including checking the created directory and file exist.

Original way making a "Projects" subfolder for the files and using a String path to the root storage folder. The String appDataDirectory is obtained with:

    private String getAppDataDirectory(final Context context){  //Get the app's data directory path.
        final PackageManager m = context.getPackageManager();
        try{
            final PackageInfo p = m.getPackageInfo(context.getPackageName(), 0);
            return p.applicationInfo.dataDir;
        }
        catch (android.content.pm.PackageManager.NameNotFoundException e) {
            if(isLogs){Log.e(TAG, "Cannot read package name: " + e.getMessage());}
            return null;
        }
    }

This is the file writing code:

       //First check projects folder exists and if not create it.
        final File projectfolder = new File(appDataDirectory + File.separator + "Projects");
        if(!projectfolder.exists()){
            if(!projectfolder.mkdirs()){ //Check for success
                if(isLogs){Log.e(TAG, "Failed to create project directory");}
                return false;
            }
        }
        final File outfile = new File(appDataDirectory + File.separator + "Projects", projectname + ".json");
        try {
            final FileOutputStream fileOutputStream = new FileOutputStream(outfile);
            final OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream);
            writer.write(json); //Output one Rect JSON per line
            writer.close();
            assert(outfile.exists());
            if(isLogs){Log.i(TAG, "Project Saved");}
            return true;
        }
        catch (IOException e) {
            if(isLogs){Log.e(TAG, "File write failed: " + e.toString());}
            return false;
        }

This checks success of projectfolder.mkdirs and outfile.exists() which are successful so the write functions appears to be normal.

I then tried other methods getting the internal storage folder more directly.

Internal Storage (this is not the full code but the relevant file write parts)

This way avoids using the File object in case this is causing the problem

final FileOutputStream fileOutputStream = context.openFileOutput(projectname + ".json", Context.MODE_PRIVATE);  
fileOutputStream.write(json.getBytes());

With File object

final File appSpecificDir  = context.getFilesDir(null);
final File outFile = new File(appSpecificDir, projectname + ".json");
final FileOutputStream fileOutputStream = new FileOutputStream(outFile);
fileOutputStream.write(json.getBytes());

External Storage

final File appSpecificExternalDir  = context.getExternalFilesDir(null);
final File outFile = new File(appSpecificExternalDir, projectname + ".json");
final FileOutputStream fileOutputStream = new FileOutputStream(outFile);
fileOutputStream.write(json.getBytes());

This was tried in the same way for internal/external cache folders using context.getCacheDir() and context.getExternalCacheDir()

However reading the files back gives "null" File objects. This is the original function to get a list of all the project names by listing files in the Projects folder:

    public static String[] listProjects(final String appDataDirectory){ //Get list of project files from the data folder.
        final File directory = new File(appDataDirectory + File.separator + "Projects");
        if(directory.exists()){ //The directory is not made until a project is saved
            final File[] files = directory.listFiles();
            if (files != null){ //Check for null to prevent error.
                final String[] projectslist = new String[files.length];
                String mystring;
                for (int i = 0; i < files.length; i++) {
                    mystring = files[i].getName();
                    projectslist[i] = mystring.substring(0, mystring.length() - 5); //Remove the ".json" to get the project name.
                    if(isLogs){Log.i(TAG, "FileName:" + mystring);}
                }
                return projectslist;
            } else{
                if(isLogs){Log.i(TAG, "Projects folder is empty");}
                return null;
            }
        } else{
            if(isLogs){Log.i(TAG, "Projects folder does not exist");}
            return null;
        }
    }

Note directory.exists() returns false and directory.listFiles() returns null after the write function has successfully completed.

I also tried the other ways to read the data that complement the other write methods shown above. These are:

final String[] files = context.fileList(); //Avoid using the File object in case this causes the problem

final File appSpecificDir = context.getFilesDir(null); final String[] files = appSpecificDir.list();``

Plus equivalents for getExternalFilesDir(), getCacheDir(), getExternalCacheDir()

Using appSpecificDir.list() it does return two files, but these are system files and the ones my app is supposed to have written are missing from the list.

In debug mode all of these methods are working normally and the app is running as planned.

I also modified these functions to write the String to Saved Preferences instead of files. This gave the following behaviour:

assert(editor.commit()); This was successful indicating that the writing of the preferences was normal.

Later in trying to read the preferences:

            if (!myPreferences.contains(projectname)){
                if(isLogs){Log.e(TAG, "Project name " + projectname + "does not exist in saved preference.");}
                return false;
            }

This returns false when it should not as this preference was written earlier.

Conclusion: The android system is somehow blocking access to the internal data folder in release mode. The file write functions are executed normally but the data is not there when I try to read it back.

I have tested by installing the release APK directly on three devices. Mostly on versions 13 but also on 10 and 11. The problem is the same for all of them.

0

There are 0 answers