2

Right now I am currently creating an app for myself that appends data to a handful of files. But when I try to create the files -- or indeed, open them at all --, the program throws java.io.IOException: Operation not permitted.

enter image description here

As you can see, storage permissions are granted. (This app is exclusively for myself, so I'm not too worried about what it would be like for other users.)

Examining the filesystem directly through the phone itself or through my computer does not show these files.

Note: creating the directories (i.e. dir.mkdirs();) does not throw an error, even if I delete the directories beforehand. Handling the files, however, does throw errors.

I am running Android 11 and my Android Studio is set to work in Lollipop.

MainActivity.java:

public class MainActivity extends AppCompatActivity {
    
    public static File dir = new File(new File(Environment.getExternalStorageDirectory(), "bleh"), "bleh");
    
    @Override protected void onCreate(Bundle savedInstanceState) {
        
        //bleh
        
        dir.mkdirs();
        dir.setWritable(true)
        addButtons();
        
    }
    
    public void addButtons() throws IOException {
        
        Option.A.init(R.id.A);
        Option.B.init(R.id.B);
        Option.C.init(R.id.C);
        Option.D.init(R.id.D);
        Option.E.init(R.id.E);
        Option.F.init(R.id.F);
        
        // This is beyond the scope of my 
        // question, but if you could also 
        // show me a better way to do this, 
        // that would be great.
        
    }
    
}

Option.java:

public enum Option {
    
    A, B, C, D, E, F, ;
    
    public File data;
    public PrintWriter printer;
    
    Option() {
        
        data = new File(MainActivity.dir, name().toLowerCase() + ".data");
        
    }
    
    public void init(int buttonid) {
        
        try {
            
            data.createNewFile();
            printer = new PrintWriter(data);
            // Both of these lines throw this exception.
            
        } catch (IOException ex) {
            
            ex.printStackTrace(System.err);
            
        }
        
    }
    
}
guninvalid
  • 411
  • 2
  • 5
  • 16

1 Answers1

15

For Android 11 just having storage permission is not enough to write the file in shared storage as we used to. Because of storage updates in Android 11: https://developer.android.com/about/versions/11/privacy/storage

So there are two ways:

1) MANAGE_EXTERNAL_STORAGE Permission

This is an easy way if you're keeping this code to yourself & not looking to upload that on play store. Because most apps won't be allowed without solid reason.

I've created a sample code.

In your manifest declare the dependency:

<uses-permission
        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        android:minSdkVersion="30" />

In your code:

You need to check if your app is already storage manager or not. Environment.isExternalStorageManager() & if not then redirect to settings ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION where you'll get option to allow. You should see you app listed there.

Once allowed you'll be able create directories/files.

public class MainActivity extends AppCompatActivity {

    public static File dir = new File(new File(Environment.getExternalStorageDirectory(), "bleh"), "bleh");


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                askForPermissions();
            }
        });

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                createDir();
            }
        }
    }

    public void askForPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            if (!Environment.isExternalStorageManager()) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
                return;
            }
            createDir();
        }
    }

    public void createDir(){
        if (!dir.exists()){
            dir.mkdirs();
        }
    }

}

You can read more about this here: https://developer.android.com/training/data-storage/manage-all-files

You can find example here: https://github.com/android/storage-samples/tree/main/FileManager

2) Follow scoped storage guidelines.

There few ways in this as well. I'll just show you how you can add file in Documents directory. Which is a shared space. For this you don't need permission.

You can use getContentResolver & insert a file to external storage & also set the relative location to DIRECTORY_DOCUMENTS.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    saveFileToExternalStorage(String.valueOf(System.currentTimeMillis()),"Test");
                }
            }
        });

    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private void saveFileToExternalStorage(String displayName, String content) {
        Uri externalUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);

        String relativeLocation = Environment.DIRECTORY_DOCUMENTS;

        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, displayName + ".txt");
        contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/text");
        contentValues.put(MediaStore.Files.FileColumns.TITLE, "Test");
        contentValues.put(MediaStore.Files.FileColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
        contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH, relativeLocation);
        contentValues.put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis());

        Uri fileUri = getContentResolver().insert(externalUri, contentValues);
        try {
            OutputStream outputStream =  getContentResolver().openOutputStream(fileUri);
            outputStream.write(content.getBytes());
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

} 

You can read more about this here: https://www.androidcentral.com/what-scoped-storage

You can find proper samples here: https://github.com/android/storage-samples/tree/main/ScopedStorage

Mayur Gajra
  • 8,285
  • 6
  • 25
  • 41