Saving a file to External Storage in the public directory on Android results in an EACCES (Permission denied) error, even though storage permission has been granted
Starting from API level 29, the method getExternalStoragePublicDirectory
is deprecated. Therefore, when attempting to write a file to Environment.DIRECTORY_DOCUMENTS
, the error 'storage/emulated/0/Documents open failed: EACCES (Permission denied)' occurs, despite having granted storage permission.
Why?
This is what Google wants exactly: To improve user privacy, direct access to shared/external storage devices is deprecated.
When an app targets Build.VERSION_CODES.Q, the path returned from this method is no longer directly accessible to apps. Apps can continue to access content stored on shared/external storage by migrating to alternatives such as Context#getExternalFilesDir(String), MediaStore, or Intent#ACTION_OPEN_DOCUMENT.
refs: https://stackoverflow.com/a/67468888, https://developer.android.com/reference/android/os/Environment#getExternalStorageDirectory()
This is my code that utilizes the deprecated method getExternalStoragePublicDirectory
/** DEPRECATED */
try {
final String fileName = "event.ics";
final BufferedSource data = response.body().source(); // response.body().source() is data response from api
// will create file in global documents directory, can be any other directory, just don't forget to handle permissions
final File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsoluteFile(), fileName);
final BufferedSink sink = Okio.buffer(Okio.sink(file));
sink.writeAll(data);
sink.close();
} catch (IOException e) {
e.printStackTrace();
}
New approach using MediaStore
import android.content.ContentValues;
import android.content.ContentResolver;
import android.net.Uri;
import okio.BufferedSource;
import java.io.OutputStream;
import okio.BufferedSink;
import java.io.IOException;
try {
final String fileName = "event.ics"; // TODO: update your file name
final String mimeType = "application/ics"; // TODO: update mime type
final String relativePath = Environment.DIRECTORY_DOCUMENTS + "/YourApp/"; // TODO: update YourApp
final BufferedSource data = response.body().source(); // response.body().source() is data response from api
// Define the content values
final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath);
// Get the content resolver
final ContentResolver resolver = context.getContentResolver();
// Insert the content values and get the content URI
final Uri collection = MediaStore.Files.getContentUri("external");
final Uri item = resolver.insert(collection, values);
// Open an output stream to write data to the content URI
final OutputStream outputStream = resolver.openOutputStream(item);
// Get buffered sink from Okio
final BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
// Read data from the response body and write to the buffered sink
bufferedSink.writeAll(data);
// Close the buffered sink
bufferedSink.close();
} catch (IOException e) {
e.printStackTrace();
}
Contact me via: https://twitter.com/BlueZoneetc