I'm trying to write to the SD card, not to the "external" storage mounted on the device itself (the one returned when calling getExternalStorageDirectory). I can read every file in the SD card, but can't change or write anything on its PUBLIC directory, retrieving this error when trying that: "java.io.IOException: open failed: EACCES (Permission denied)".
I saw many answers to problems like that here, but most only talk about the "external storage" (primary), and this is not the SD card (on most devices nowadays). I know that I can write to my app's private directory in SD card, but that's not what I'm looking for, I want to change public files. Finally, some people told me I cannot write to sd card's public directory in Android 6.0 Marshmallow, but I don't believe Android would impair developers this way.
Note: I am requesting permission at runtime, but the error persists even after being authorized at runtime, being authorized directly in the application manager, with the device connected or disconnected from the usb.
The code I'm using is below:
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import static android.widget.Toast.LENGTH_LONG;
public class MainActivity extends AppCompatActivity {
private static String TAG = "SdCardTest";
private static final int REQUEST_WRITE_STORAGE = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int permission = ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission to record denied");
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Permission is required to access SD card.").setTitle("Permission required");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Log.i(TAG, "Clicked");
makeRequest();
}
});
AlertDialog dialog = builder.create();
dialog.show();
} else {
makeRequest();
}
}
}
protected void makeRequest() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_WRITE_STORAGE: {
if (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission has been denied by user");
Toast.makeText(this, "Permission has been denied by user", LENGTH_LONG).show();
} else {
Log.i(TAG, "Permission has been granted by user");
Toast.makeText(this, "Permission has been granted by user", LENGTH_LONG).show();
//Here I WRITE to SD card
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "Not working", LENGTH_LONG).show();
}
}
return;
}
}
}
}
My Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.patrick.testasdcompermissaoemexecucao">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Thanks to @VladMatvienko, I was able to write a code that allows me to read, write and edit any directory or subdirectory on my SD card.
This is my complete code that you can copy and paste in your MainActivity to test the Storage Access Framework:
import android.app.Activity;
import android.content.Intent;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.v4.provider.DocumentFile;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class MainActivity extends AppCompatActivity {
int SAVE_REQUEST_CODE = 102;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void saveFile(View view) {
List<UriPermission> permissions = getContentResolver().getPersistedUriPermissions();
if (permissions != null && permissions.size() > 0) {
//pickedDir is the directory the user selected (I chose SD's root).
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, permissions.get(0).getUri());
//=====You can use these lines to write into an existent folder or directory========================
DocumentFile aux = pickedDir.findFile("aaabrao");//aaabrao is the name of a existing folder in my SD.
//Use DocumentFile file = pickedDir.createDirectory("aaabrao"); to create a new folder in the pickedDir directory.
if(aux != null) {
//Creating the object "file" is essencial for you to write "some text" INSIDE the TXT file. Otherwise, your TXT will be a blank.
DocumentFile file = aux.createFile("text/plain", "try5.txt");
writeFileContent(file.getUri());
}
//==================================================================================================
//=====You can use these lines to write to the primary chosen directory (SD's root, in my case)====
DocumentFile file2 = pickedDir.createFile("text/plain", "try5.txt");
writeFileContent(file2.getUri());
//==================================================================================================
} else {
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), SAVE_REQUEST_CODE);
}
}
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == SAVE_REQUEST_CODE) {
if (resultData != null) {
Uri treeUri=resultData.getData();
//This line gets your persistent permission to write SD (or anywhere else) without prompting a request everytime.
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//These lines will write to the choosen directory (SD's root was my chosen one). You could also write to an existent folder inside SD's root like me in saveFile().
DocumentFile pickedDir = DocumentFile.fromTreeUri(getBaseContext(), treeUri);
DocumentFile file = pickedDir.createFile("text/plain", "try5.txt");
writeFileContent(file.getUri());
}
}
}
}
private void writeFileContent(Uri uri) {
try {
ParcelFileDescriptor pfd = this.getContentResolver().openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
String textContent = "some text";
fileOutputStream.write(textContent.getBytes());
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I made this activity_main to call the saveFile() using a button:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.patrick.testasdcompermissaoemexecucao.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button2"
android:text="Write"
android:onClick="saveFile"
android:layout_below="@+id/imageButton"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
tools:ignore="UnknownId"
android:layout_gravity="center" />
</RelativeLayout>
You must change
tools:context="com.example.patrick.testasdcompermissaoemexecucao.MainActivity">
for your app's directory. If you don't know how, create a blank project and copy the equivalent line before pasting my code there.
No changes are needed in the Manifest file.
Good Luck!