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

Steve Blue
2 min readMay 13, 2024

--

shared storage / documents / my file (event.ics)

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();
}

--

--

Steve Blue

Experienced Mobile Application Developer with a demonstrated history of working in the computer software industry.