Request write permission for SD Card
Micro SD cards are read only on modern Android devices.
Only one app specific directory is writable.
On Android Q+ the card is not even readable except for that directory.
You can however use SAF for full access.
Write a file in the SD card of an Android 7 device
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;
}
Android - ask write to SD card permission dialog
I found open source file manager. Looked at the code and finally found the solution.
Only works on Android N (7.0.0) (api 24) and above.
First get root path of SD card and show user permission dialog.
public void takeCardUriPermission(String sdCardRootPath) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
File sdCard = new File(sdCardRootPath);
StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
StorageVolume storageVolume = storageManager.getStorageVolume(sdCard);
Intent intent = storageVolume.createAccessIntent(null);
try {
startActivityForResult(intent, 4010);
} catch (ActivityNotFoundException e) {
}
}
}
When user accepts the request, onActivityResult() gets triggered and we can save uri from intent data
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 4010) {
Uri uri = data.getData();
grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
}
}
Now we can retrieve uri of SD card root and use it with Storage Access Framework
public Uri getUri() {
List<UriPermission> persistedUriPermissions = getContentResolver().getPersistedUriPermissions();
if (persistedUriPermissions.size() > 0) {
UriPermission uriPermission = persistedUriPermissions.get(0);
return uriPermission.getUri();
}
return null;
}
No permission to WRITE on SD Card
You have to handle runtime permission for this,Take a look in this steps
Copy this Class:
public class PermissionHandler {
// Run Time Permission List
private static final String MNC = "MNC";
// Calendar group.
public static final String READ_CALENDAR = Manifest.permission.READ_CALENDAR;
public static final String WRITE_CALENDAR = Manifest.permission.WRITE_CALENDAR;
// Camera group.
public static final String CAMERA = Manifest.permission.CAMERA;
// Contacts group.
public static final String READ_CONTACTS = Manifest.permission.READ_CONTACTS;
public static final String WRITE_CONTACTS = Manifest.permission.WRITE_CONTACTS;
// Location group.
public static final String ACCESS_FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
public static final String ACCESS_COARSE_LOCATION = Manifest.permission.ACCESS_COARSE_LOCATION;
// Microphone group.
public static final String RECORD_AUDIO = Manifest.permission.RECORD_AUDIO;
// Phone group.
public static final String READ_PHONE_STATE = Manifest.permission.READ_PHONE_STATE;
public static final String CALL_PHONE = Manifest.permission.CALL_PHONE;
public static final String READ_CALL_LOG = Manifest.permission.READ_CALL_LOG;
public static final String WRITE_CALL_LOG = Manifest.permission.WRITE_CALL_LOG;
public static final String ADD_VOICEMAIL = Manifest.permission.ADD_VOICEMAIL;
public static final String USE_SIP = Manifest.permission.USE_SIP;
public static final String PROCESS_OUTGOING_CALLS = Manifest.permission.PROCESS_OUTGOING_CALLS;
// Sensors group.
public static final String BODY_SENSORS = Manifest.permission.BODY_SENSORS;
public static final String USE_FINGERPRINT = Manifest.permission.USE_FINGERPRINT;
// SMS group.
public static final String SEND_SMS = Manifest.permission.SEND_SMS;
public static final String RECEIVE_SMS = Manifest.permission.RECEIVE_SMS;
public static final String READ_SMS = Manifest.permission.READ_SMS;
public static final String RECEIVE_WAP_PUSH = Manifest.permission.RECEIVE_WAP_PUSH;
public static final String RECEIVE_MMS = Manifest.permission.RECEIVE_MMS;
public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
// Bookmarks group.
public static final String READ_HISTORY_BOOKMARKS = "com.android.browser.permission.READ_HISTORY_BOOKMARKS";
public static final String WRITE_HISTORY_BOOKMARKS = "com.android.browser.permission.WRITE_HISTORY_BOOKMARKS";
public static boolean isPermissionGranted(Activity mContext, String Permission, String Text, int PermissionCode) {
if (ContextCompat.checkSelfPermission(mContext, Permission) != PackageManager.PERMISSION_GRANTED) {
reqPermission(mContext, Text, PermissionCode, Permission);
return false;
}
return true;
}
public static void reqPermission(Activity mContext, String Text, int PermissionCode, String Permission) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(mContext, Permission)) {
ActivityCompat.requestPermissions(mContext, new String[]{Permission}, PermissionCode);
} else {
openAlertDialog(mContext, Text);
}
}
public static void openAlertDialog(final Context mContext, String Text) {
new AlertDialog.Builder(mContext)
.setTitle("Permission")
.setMessage(mContext.getResources().getString(R.string.app_name) + " needs to access " + Text + " Permission for using this features from Setting >> Permissions.")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// Your code
String packageName = "you.App.Id";
try {
//Open the specific App Info page:
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
//e.printStackTrace();
//Open the generic Apps page:
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS);
mContext.startActivity(intent);
}
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
}
Before you save image you need to check this
if (PermissionHandler.isPermissionGranted((Activity) mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE, "External Storage", 1000)) {
// Save your Image code
}
Thats it.Happy Coding :)
Android M write to SD Card - Permission Denied
As suggested by @CommonsWare here we have to use the new Storage Access Framework provided by android and will have to take permission from user to write SD card file as you said this is already written in the File Manager Application ES File Explorer.
Here is the code for Letting the user choose the "SD card" :
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), requestCode);
which will look somewhat like this :
And get the Document path in pickedDir
and pass further in your copyFile block
and use this path for writing the file :
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode != RESULT_OK)
return;
else {
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
copyFile(sdCard.toString(), "/File.txt", path + "/new", pickedDir);
}
}
public void copyFile(String inputPath, String inputFile, String outputPath, DocumentFile pickedDir) {
InputStream in = null;
OutputStream out = null;
try {
//create output directory if it doesn't exist
File dir = new File(outputPath);
if (!dir.exists()) {
dir.mkdirs();
}
in = new FileInputStream(inputPath + inputFile);
//out = new FileOutputStream(outputPath + inputFile);
DocumentFile file = pickedDir.createFile("//MIME type", outputPath);
out = getContentResolver().openOutputStream(file.getUri());
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
// write the output file (You have now copied the file)
out.flush();
out.close();
} catch (FileNotFoundException fnfe1) {
/* I get the error here */
Log.e("tag", fnfe1.getMessage());
} catch (Exception e) {
Log.e("tag", e.getMessage());
}
}
Has SD card writing been blocked?
Yes writing to the sd card is blocked in modern Android versions.
Mostly you have read acces to the whole sd card.
Writing only to one app specific directory which if you are lucky is available in the second item returned by getExternalFilesDirs()
.
If you want to write to the whole sd card then use the Storage Access Framework.
For instance Intent.ACTION_OPEN_DOCUMENT_TREE.
Related Topics
Classcastexception When Casting to the Same Class
Ssl Handshake Alert: Unrecognized_Name Error Since Upgrade to Java 1.7.0
Is Multi-Thread Output from System.Out.Println Interleaved
How to Parse JSON Array with Gson
How to Return an Array from Jni to Java
Android N Change Language Programmatically
Equivalent of Data Protection API on Linux
Decibel Values at Specific Points in Wav File
Variable Column Names Using Prepared Statements
Math.Random() Versus Random.Nextint(Int)
Calling a Servlet from Jsp File on Page Load
Execute Shell Command from Android