Search code examples
androidpermission-deniedandroid-external-storagewritefile

Write a file in the SD card of an Android 7 device


I have to develop a little function that, besides other things, writes a file in the SD card. I have to use it for two specific Android tablets provided by a supplier. One tablet uses Android 5 and the other uses Android 7. The application that I am modifying is a system app and it doesn't have UI. I'm calling the code from a Service, and I want to call it from a FirebaseMessagingService. I have problems to write a file only in Android 7 tablet.

I have no problems with the Android 5 tablet, I identified the external storage folder and I can create files in it. But I do have problems in Android 7, I identified the external storage folder and I have a problem: Permission denied.

I have this permissions in the manifest:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

This is the piece of code that is giving me problems:

public void myFunction()
{
    String sdPath;
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP)
        sdPath = "/storage/extsd";
    else
        sdPath = "/storage/0665-3426";

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (my_context.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
            Log.d(TAG, "Permission is granted");
        else
            Log.d(TAG, "Permission is denied");
    }

    File folder = new File(sdPath);
    if (folder.exists()) {
        Log.d(TAG, sdPath + " exists, can write: " + folder.canWrite());
        File file = new File(sdPath + "/new_file");
        boolean fileExists = file.exists();
        Log.d(TAG, file.getAbsolutePath() + " file exists: " + fileExists + ", can write: " + file.canWrite());

        if (!fileExists) {
            try {
                file.createNewFile();
                Log.d(TAG, "Can write in " + sdPath);
            }
            catch (Exception exception) {
                Log.e(TAG, "Cannot write in " + sdPath + ": " + exception.getMessage());
            }
        }
    }
    else
        Log.e(TAG, sdPath + " does not exist.");

    ...
}

Here the logs in Android 5 tablet:

10-22 14:44:51.271 10450-10450/com.my.app D/MY_TAG: /storage/extsd exists, can write: true
10-22 14:44:51.368 10450-10450/com.my.app D/MY_TAG: /storage/extsd/new_file file exists: false, can write: false
10-22 14:44:51.479 10450-10450/com.my.app D/MY_TAG: Can write in /storage/extsd

And here the logs in Android 7 tablet:

2020-10-22 15:11:56.383 19689-19689/com.my.app D/MY_TAG: Permission is granted
2020-10-22 15:11:59.037 19689-19689/com.my.app D/MY_TAG: /storage/0665-3426 exists, can write: false
2020-10-22 15:12:07.956 19689-19689/com.my.app D/MY_TAG: /storage/0665-3426/new_file file exists: false, can write: false
2020-10-22 15:12:07.957 19689-19689/com.my.app E/MY_TAG: Cannot write in /storage/0665-3426: Permission denied

As you can see, even if permission is granted, canWrite() method returns false in Android 7. Do you know the reason? How can I solve this problem?

I have read some other questions from stack overflow but I didn't find the solution.


Solution

  • I have decided not to use the Android API. Since the application has elevated privileges, I have created the file by executing a shell command. This is the code to create a file (works with removable SD card folder):

    public static String createFile(String filePath)
    {
        String returnValue = "";
    
        try
        {
            Runtime runtime = Runtime.getRuntime();
    
            String[] command = new String[]{ "su", "0", "touch", filePath};
    
            Process p = runtime.exec(command);
            p.waitFor();
    
            java.io.BufferedReader errorIn = new java.io.BufferedReader(
                    new java.io.InputStreamReader(p.getErrorStream()));
    
            String line = "";
            while ((line = errorIn.readLine()) != null)
                returnValue += line + "\n";
    
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
    
        return returnValue;
    }