Has SD card writing been blocked? - android

I am trying to provide a functionality in my app that the media or storage files used in my application can be moved to SD card by the user.
I am trying to use the code as described in below link
http://www.java-samples.com/showtutorial.php?tutorialid=1523
But I get a permission exception. When I searched for getting that permission, I see that I have to root the device. I don't want to root my device, as it is illegal, no? Is there any android device model that comes rooted from the beginning itself from the manufacturer?
Earlier also I used to see a "Move To SD Card" option in the app settings, but I don't see that option any more. I also saw that most of the file browser applications installed in my device are unable to create a folder on the SD card,
Please share some light on what's the best recommended way to implement this feature. We are supporting android 4.4 to 8.0

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.

If you haven't done so already, you will need to give your app the correct permission to write to the SD Card by adding the line below to your Manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Getting runtime permissions
You should be checking if the user has granted permission of external storage by using:
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
//File write logic here
return true;
}
If not, you need to ask the user to grant your app a permission:
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
Of course these are for marshmallow devices only so you need to check if your app is running on Marshmallow:
if (Build.VERSION.SDK_INT >= 23) {
//do your check here
}
Be also sure that your activity implements OnRequestPermissionResult
The entire permission looks like this:
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
Permission result callback:
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults[0]== PackageManager.PERMISSION_GRANTED){
Log.v(TAG,"Permission: "+permissions[0]+ "was "+grantResults[0]);
//resume tasks needing this permission
}
}
Also
SD Card directory is /sdcard but you shouldn't be hard coding it. Instead, make a call to Environment.getExternalStorageDirectory() to get the directory:
File sdDir = Environment.getExternalStorageDirectory();
Code to write into external storage
Source
/** Method to check whether external media available and writable. This is adapted from
http://developer.android.com/guide/topics/data/data-storage.html#filesExternal */
private void checkExternalMedia(){
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// Can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// Can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Can't read or write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
}
/** Method to write ascii text characters to file on SD card. Note that you must add a
WRITE_EXTERNAL_STORAGE permission to the manifest file or this method will throw
a FileNotFound Exception because you won't have write permission. */
private void writeToSDFile(){
// Find the root of the external storage.
// See http://developer.android.com/guide/topics/data/data- storage.html#filesExternal
File root = android.os.Environment.getExternalStorageDirectory();
// See http://stackoverflow.com/questions/3551821/android-write-to-sd-card-folder
File dir = new File (root.getAbsolutePath() + "/download");
dir.mkdirs();
File file = new File(dir, "myData.txt");
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.println("Hi , How are you");
pw.println("Hello");
pw.flush();
pw.close();
f.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.i(TAG, "******* File not found. Did you" +
" add a WRITE_EXTERNAL_STORAGE permission to the manifest?");
} catch (IOException e) {
e.printStackTrace();
}
}
/** Method to read in a text file placed in the res/raw directory of the application. The
method reads in all lines of the file sequentially. */
private void readRaw(){
InputStream is = this.getResources().openRawResource(R.raw.textfile);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr, 8192); // 2nd arg is buffer size
// More efficient (less readable) implementation of above is the composite expression
/*BufferedReader br = new BufferedReader(new InputStreamReader(
this.getResources().openRawResource(R.raw.textfile)), 8192);*/
try {
String test;
while (true){
test = br.readLine();
// readLine() returns null if no more lines in the file
if(test == null) break;
}
isr.close();
is.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
In addition to that
Shared storage may not always be available, since removable media can be ejected by the user.
Media state can be checked using getExternalStorageState(File).
There is no security enforced with these files.

Related

Android M write to SD Card - Permission Denied

I'm trying to copy file from within my application to the SD card, but I get the error eacces (permission denied). The OS is Android M and I have allowed runtime Storage permissions (checked in app info). I have also set the uses-permission in AndroidManifest.xml
<application>...</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Doesn't work if I copy to SD card
Source: data/user/0/com.example.myapp/cache/SomeFile.txt
Destination: /storage/1032-2568/SomeFolder/
Error: java.io.FileNotFoundException: /storage/1032-2568/SomeFolder/SomeFile.txt: open failed: EACCES (Permission denied)
Works if I copy to internal storage
Source: data/user/0/com.example.myapp/cache/SomeFile.txt
Destination: /storage/emulated/0/SomeFolder/
Code to copy file from source to destination
/*
* Below are the parameters I have tried
*
* inputPath - data/user/0/com.example.myapp/cache or data/user/0/com.example.myapp/cache/
* inputFile - /SomeFile.txt or SomeFile.txt
* outputPath - /storage/1032-2568/SomeFolder/ or /storage/1032-2568/SomeFolder
*/
public static void copyFile(String inputPath, String inputFile, String outputPath) {
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);
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());
}
}
ES File Explorer
I saw that ES File Explorer also cannot write anything on the SD Card on Redmi devices. Here's a video with solution. Following the steps worked for ES Explorer on my device. Can this be done programmatically?
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 pickedDirand 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());
}
}
You need to add permission request run time in Android 6.0 (API Level 23) and up, here is the official docs
This is the code for WRITE_EXTERNAL_STORAGE
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG,"Permission is granted");
return true;
}
Ask for permission else like this
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
I have also got that problem but i solved by use the request the permission in run time and after forcefully give the permission.After the permission in App info of Android device. after declare the permission in manifest =>go to setting of your device => go to app info => go to permission =>
and finally allow the permission . just remember i just talking about after api level 22 means from marshmallow.
Its seems the runtime permission are implemented correctly but the issues seems from the device
If you are using Redmi than you have to manually allow the permission of specific app in Redmi security settings
This link shows how to enable permission in redmi security
After Android 4.3 on some devices, you can't get direct write access to FileSystem on SDcard.
You should use storage access framework for that.
I can see that you are copying the entire content of one file and trying to write the same to another file. I could suggest a better way to do this :
Assuming that you already checked for file existence
StringWriter temp=new StringWriter();
try{
FileInputStream fis=new FileInputStream(inputFile+inputPath);
int i;
while((i=fis.read())!=-1)
{
temp.write((char)i);
}
fis.close();
FileOutputStream fos = new FileOutputStream(outputPath, false); // true or false based on opening mode as appending or writing
fos.write(temp.toString(rs1).getBytes());
fos.close();
}
catch (Exception e){}
This code worked for my app...Let me know if this is working for you or not..
You can't copy or Delete files & Folder on external storage using third party app. like [file explorer].
It's data policy updated after KITKAT Version.
If only allow on system apps. So you can use an original file explorer (Come from ROM).
IF you need to use 3rd party app then ROOT your device. (Root permission is required)

Android 6.0 Write to external SD Card

I tried everything to write on external SD card on Android 6.0, but I am not able to write on it.
I did research on stackoverflow and found lot of things but none works. Here is my code
String extPath = System.getenv("SECONDARY_STORAGE") + "/Android/data/com.gvm.externalstorage.externalstoragetest/";
File file = new File(extPath,"myFiles");
if (!file.exists()) {
boolean dirResult = file.mkdirs();
Log.e("Directory Exist", dirResult + " Directory created");
} else {
Log.e("Directory Exist", "Exist");
Log.e("Direcotry Path",file.getAbsolutePath());
}
//String displayname = fileName.replace("%20", " ");
File outputFile = new File(file, "mytest5.txt");
outputFile.createNewFile();
This code works on Android 5.0 but not on Android 6.0.
Then I tried this path as well, and that gives me permission error, I have set all permission and managed code for runtime permission as well.
/mnt/media_rw/6AC9-083B
File write failed: java.io.IOException: open failed: EACCES (Permission denied)
If anyone can help me it would be great as I am trying this since last 3 days.
Thanks,
Anvesh
After long hard work I figured out a solution. In Android 6.0 it's not going to give you SD Card path always using this:
System.getenv("SECONDARY_STORAGE")
or this
Environment.getExternalStorageDirectory()
So I retrieved external SD Card path using this
File[] fs = context.getExternalFilesDirs(null);
String extPath = "";
// at index 0 you have the internal storage and at index 1 the real external...
if (fs != null && fs.length >= 2)
{
extPath = fs[1].getAbsolutePath();
Log.e("SD Path",fs[1].getAbsolutePath());
}
Rest everything will remain same for permission and all.
Thanks to those who helped me.
From API 23+(6.0) you need to request the read/write permissions even if they are already in your manifest known as Requesting Permissions at Run Time.
from docs
Beginning in Android 6.0 (API level 23), users grant permissions to
apps while the app is running, not when they install the app. This
approach streamlines the app install process, since the user does not
need to grant permissions when they install or update the app. It also
gives the user more control over the app's functionality; for example,
a user could choose to give a camera app access to the camera but not
to the device location. The user can revoke the permissions at any
time, by going to the app's Settings screen.
java
// Storage Permissions
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
/**
* Checks if the app has permission to write to device storage
*
* If the app does not has permission then the user will be prompted to grant permissions
*
* #param activity
*/
public static void verifyStoragePermissions(Activity activity) {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
);
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
I think u should check your app permission first, to make sure your storage permission has been turned on.
If there's no storage permission:
Please check if u use this permission in your AndroidManifest
<uses-permission android:name="android.permission.STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
If the storage permission has been turned off:
Please check your runtime permission, maybe u can refer to this code
private void checkPermissions() {
if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return;
}
final List<String> permissionsList = new ArrayList<String>();
permissionsList.add(Manifest.permission.ACCESS_COARSE_LOCATION);
permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
permissionsList.add(Manifest.permission.WRITE_CALENDAR);
permissionsList.add(Manifest.permission.READ_PHONE_STATE);
int permissionCheckLocation = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION);
int permissionCheckStorage = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int permissionCheckCalendar = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.WRITE_CALENDAR);
int permissionCheckPhoneState = ContextCompat.checkSelfPermission(IntroductionActivity.this, Manifest.permission.READ_PHONE_STATE);
boolean locationPermission=permissionCheckLocation == PackageManager.PERMISSION_GRANTED?true:false;
boolean storagePermission=permissionCheckStorage == PackageManager.PERMISSION_GRANTED?true:false;
boolean calendarPermission=permissionCheckCalendar == PackageManager.PERMISSION_GRANTED?true:false;
boolean phoneStatePermission=permissionCheckPhoneState == PackageManager.PERMISSION_GRANTED?true:false;
boolean shouldShowLocationPermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION);
boolean shouldShowStoragePermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
boolean shouldShowCalendarPermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.WRITE_CALENDAR);
boolean shouldShowPhoneStatePermission=ActivityCompat.shouldShowRequestPermissionRationale(IntroductionActivity.this, Manifest.permission.READ_PHONE_STATE);
if (permissionCheckLocation == PackageManager.PERMISSION_GRANTED && permissionCheckStorage == PackageManager.PERMISSION_GRANTED
&& permissionCheckCalendar == PackageManager.PERMISSION_GRANTED && permissionCheckPhoneState == PackageManager.PERMISSION_GRANTED){
return;
}else if(((!locationPermission&&!shouldShowLocationPermission)||(!storagePermission&&!shouldShowStoragePermission)
||(!calendarPermission&&!shouldShowCalendarPermission)||(!phoneStatePermission&&!shouldShowPhoneStatePermission))&&appContext.localCheckPermission){
showMessageOKCancel("You need to allow access these permissions",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
});
}else{
ActivityCompat.requestPermissions(IntroductionActivity.this, permissionsList.toArray(new String[permissionsList.size()]), 0);
}
}
If still have problem, please try to change your file path :
String fileName="mytest5.txt";
File folder = new File(Environment.getExternalStorageDirectory().getPath() + "/com.gvm.externalstorage.externalstoragetest/");
if (!folder.exists()) {
try {
folder.mkdirs();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Default Save Path Creation Error:" + folder);
}
}
File logFile = new File(folder, fileName);
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
System.out.println("Default Save Path Creation Error:" + logFile);
}
}
Best regards. I hope this can help u
#Anvesh Another reliable method i'm using:
/**
* Get external storage path use reflect on android 6.0 device.
* Source code:
* https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/storage/StorageVolume.java
*
* #param removable the sdcard can remove or not, true means external sdcard, false means
* internal sdcard.
* #return path of sdcard we want
*/
public static String getStoragePath(boolean removable) {
WinZipApplication application = WinZipApplication.getInstance();
Context mContext = application.getApplicationContext();
StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = null;
try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String path = (String) getPath.invoke(storageVolumeElement);
boolean mRemovable = (Boolean) isRemovable.invoke(storageVolumeElement);
if (removable == mRemovable) {
return path;
}
}
} catch (Exception e) {
return null;
}
return null;
}
After a lot of research I found ABSOLUTE SOLUTION. IT WORKS.
public boolean checkStorage() {
File[] fs = con.getExternalFilesDirs(null);
if (fs.length == 2)
return true;
else
return false;
}

mkdir() works while inside internal flash storage, but not SD card?

I am currently building a file management app that allows the user to browse the file system of their device. The user starts off in the root directory / of their device, but can browse to any location they want such as the internal flash storage or SD card.
One of the critical requirements of this app is to allow the user to create new folders anywhere. A feature like this would be immensely useful for the app. However, the File#mkdir() method does not work at all in the SD card directory.
I added the appropriate permissions to the manifest file. I also wrote a test to see which directories (all of which exist on my Lollipop 5.0 device) allow the creation of a new folder. From my observations, File#mkdir() only works when inside the internal flash storage directory.
Note: please don't confuse Environment#getExternalStorageDirectory() with the SD card location, as explained by this article. Also on Lollipop 5.0, I believe /storage/emulated/0/ and /storage/sdcard0/ refer to the internal flash storage while /storage/emulated/1/ and /storage/sdcard1/ refer to the SD card (which is at least true for the device I am testing with).
How can I create new files and folders in areas outside the external storage path on non-rooted Android devices?
Manifest:
...
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
Test:
...
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final String NEW_FOLDER_NAME = "TestFolder";
testPath(new File(Environment.getExternalStorageDirectory(), NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/0/", NEW_FOLDER_NAME));
testPath(new File("/storage/emulated/1/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard0/Download/", NEW_FOLDER_NAME));
testPath(new File("/storage/sdcard1/Pictures/", NEW_FOLDER_NAME));
}
private void testPath(File path) {
String TAG = "Debug.MainActivity.java";
String FOLDER_CREATION_SUCCESS = " mkdir() success: ";
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
}
}
Output:
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/0/TestFolder mkdir() success: true
/storage/emulated/1/TestFolder mkdir() success: false
/storage/sdcard0/Download/TestFolder mkdir() success: true
/storage/sdcard1/Pictures/TestFolder mkdir() success: false
First, you should note that file.mkdir() and file.mkdirs() returns false if the directory already existed. If you want to know whether the directory exists on return, either use (file.mkdir() || file.isDirectory()) or simply ignore the return value and call file.isDirectory() (see the documentation).
That said, your real problem is that you need permission to create the directory on removable storage on Android 5.0+. Working with removable SD cards on Android is horrendous.
On Android 4.4 (KitKat), Google restricted access to SD cards (see here, here, and here). See this StackOverflow answer which leads to this XDA post if you need to create a directory on a removable SD card on Android 4.4 (KitKat).
On Android 5.0 (Lollipop), Google introduced new SD card access APIs. For sample usage please refer to this stackoverflow answer.
Basically, you need to use DocumentFile#createDirectory(String displayName) to create your directory. You will need to ask the user to grant permissions to your app before creating this directory.
NOTE: This is for removable storage. Using File#mkdirs() will work on internal storage (which is often confused with external storage on Android) if you have the permission android.permission.WRITE_EXTERNAL_STORAGE.
I will post some example code below:
Check if you need to ask for permission:
File sdcard = ... // the removable SD card
List<UriPermission> permissions = context.getContentResolver().getPersistedUriPermissions();
DocumentFile documentFile = null;
boolean needPermissions = true;
for (UriPermission permission : permissions) {
if (permission.isWritePermission()) {
documentFile = DocumentFile.fromTreeUri(context, permission.getUri());
if (documentFile != null) {
if (documentFile.lastModified() == sdcard.lastModified()) {
needPermissions = false;
break;
}
}
}
}
Next (if needPermissions is true), you can display a dialog to explain to the user that they need to select the "SD Card" to give your app permissions to create files/directories and then start the following activity:
if (needPermissions) {
// show a dialog explaining that you need permission to create the directory
// here, we will just launch to chooser (what you need to do after showing the dialog)
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), STORAGE_REQUEST_CODE);
} else {
// we already have permission to write to the removable SD card
// use DocumentFile#createDirectory
}
You will now need to check the resultCode and requestCode in onActivityResult:
#Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == STORAGE_REQUEST_CODE && resultCode == RESULT_OK) {
File sdcard = ... // get the removable SD card
boolean needPermissions = true;
DocumentFile documentFile = DocumentFile.fromTreeUri(MainActivity.this, data.getData());
if (documentFile != null) {
if (documentFile.lastModified() == sdcard.lastModified()) {
needPermissions = false;
}
}
if (needPermissions) {
// The user didn't select the "SD Card".
// You should try the process over again or do something else.
} else {
// remember this permission grant so we don't need to ask again.
getContentResolver().takePersistableUriPermission(data.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Now we can work with DocumentFile and create our directory
DocumentFile doc = DocumentFile.fromTreeUri(this, data.getData());
// do stuff...
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
That should give you a good start on working with DocumentFile and removable SD cards on Android 5.0+. It can be a PITA.
Also, there is no public API to get the path to a removable SD card (if one even exists). You should not rely on hardcoding "/storage/sdcard1"! There are quite a few posts about it on StackOverflow. Many of the solutions use the environment variable SECONDARY_STORAGE. Below is two methods you can use to find removable storage devices:
public static List<File> getRemovabeStorages(Context context) throws Exception {
List<File> storages = new ArrayList<>();
Method getService = Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class);
if (!getService.isAccessible()) getService.setAccessible(true);
IBinder service = (IBinder) getService.invoke(null, "mount");
Method asInterface = Class.forName("android.os.storage.IMountService$Stub")
.getDeclaredMethod("asInterface", IBinder.class);
if (!asInterface.isAccessible()) asInterface.setAccessible(true);
Object mountService = asInterface.invoke(null, service);
Object[] storageVolumes;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String packageName = context.getPackageName();
int uid = context.getPackageManager().getPackageInfo(packageName, 0).applicationInfo.uid;
Method getVolumeList = mountService.getClass().getDeclaredMethod(
"getVolumeList", int.class, String.class, int.class);
if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
storageVolumes = (Object[]) getVolumeList.invoke(mountService, uid, packageName, 0);
} else {
Method getVolumeList = mountService.getClass().getDeclaredMethod("getVolumeList");
if (!getVolumeList.isAccessible()) getVolumeList.setAccessible(true);
storageVolumes = (Object[]) getVolumeList.invoke(mountService, (Object[]) null);
}
for (Object storageVolume : storageVolumes) {
Class<?> cls = storageVolume.getClass();
Method isRemovable = cls.getDeclaredMethod("isRemovable");
if (!isRemovable.isAccessible()) isRemovable.setAccessible(true);
if ((boolean) isRemovable.invoke(storageVolume, (Object[]) null)) {
Method getState = cls.getDeclaredMethod("getState");
if (!getState.isAccessible()) getState.setAccessible(true);
String state = (String) getState.invoke(storageVolume, (Object[]) null);
if (state.equals("mounted")) {
Method getPath = cls.getDeclaredMethod("getPath");
if (!getPath.isAccessible()) getPath.setAccessible(true);
String path = (String) getPath.invoke(storageVolume, (Object[]) null);
storages.add(new File(path));
}
}
}
return storages;
}
public static File getRemovabeStorageDir(Context context) {
try {
List<File> storages = getRemovabeStorages(context);
if (!storages.isEmpty()) {
return storages.get(0);
}
} catch (Exception ignored) {
}
final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
if (SECONDARY_STORAGE != null) {
return new File(SECONDARY_STORAGE.split(":")[0]);
}
return null;
}
path.mkdir() fails also when the directory already exists.
You can add a check first:
if (!path.exists()) {
boolean success = path.mkdir();
Log.d(TAG, path.getAbsolutePath() + FOLDER_CREATION_SUCCESS + success);
path.delete();
} else {
Log.d(TAG, path.getAbsolutePath() + "already exists");
}
on Kitkat google restricted access to external sdcard so u wont be able to write to external Storage on Kitkat.
In Lollipop google made a new FrameWork to write data to external storage u have to use the new DocumentFile
class which is backward compatible .
Basically u can request the permission at onstart of app to the root directory of the app and then u can create directory
Try with this. It works fine for me.
final String NEW_FOLDER_NAME = "TestFolder";
String extStore = System.getenv("EXTERNAL_STORAGE");
File f_exts = new File(extStore, NEW_FOLDER_NAME);
String secStore = System.getenv("SECONDARY_STORAGE");
File f_secs = new File(secStore, NEW_FOLDER_NAME);
testPath(f_exts);
textPath(f_secs);
and change boolean value in testPath function as follows
boolean success;
if(path.exists()) {
// already created
success = true;
} else {
success = path.mkdir();
}
If folder already exists, path.mkdir() method return false.
and done.!!!
reference from this question.

SD Card and Configuration Settings

Oh Dear, Please help before I have no more hair on my head!
Firstly, here is my code:
private void copyDatabase() {
if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
Toast.makeText(this, "External SD card not mounted", Toast.LENGTH_LONG).show();
}
//Toast Message Always appears
try {
InputStream in = getAssets().open("Asset_Directory");
File outFile = new File(Environment.getExternalStorageDirectory() + File.separator + "Asset_Directory");
outFile.createNewFile();
OutputStream out = new FileOutputStream(outFile);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
in.close();
Log.i("AssetCaptureActivity","Database has been transferred");
} catch (IOException e) {
Log.i("AssetCaptureActivity","Could not open the file");
}
}`
As you can see:
I copied a database file into my Assets folder.
Now I want to copy it to a virtual sd card so that I can edit the database.
I have added the WRITE permissions in my manifest.
I read that I need to change something in my [run configuration] setting - but what?
I keep getting Permission Denied and It says my sd card is not mounted.
Please help!
You can use following methods to determine whether external storage is available or not
/**
* #return <ul>
* <li><b>true: </b>If external storage is available</li>
* <li><b>false: </b>If external storage is not available</li>
* </ul>
*/
public static boolean isExternalStorageAvailable() {
boolean isAvailable = false;
try {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can read the media
isAvailable = true;
} else {
// Something else is wrong. It may be one of many other states,
// but all we need
// to know is we can not read
isAvailable = false;
}
} catch (Exception e) {
e.printStackTrace();
}
return isAvailable;
}
/**
* #return <ul>
* <li><b>true: </b>If external storage is writable</li>
* <li><b>false: </b>If external storage is not writable</li>
* </ul>
*/
public static boolean isExternalStorageWritable() {
boolean isWriteable = false;
try {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can write the media
isWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media but we can't write
isWriteable = false;
} else {
// Something else is wrong. It may be one of many other
// states, but all we need
// to know is we can neither read nor write
isWriteable = false;
}
} catch (Exception e) {
e.printStackTrace();
}
return isWriteable;
}
Add following permissions in Android Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Also check whether you have enabled sd-card for emulator:
Go to 'Android Virtual Device Manager'(AVD) and click on your Emulater then press 'Edit' button. In 'Hardware' section, check whether you have added 'SD card support = yes'

Creating a directory in /sdcard fails

I have been trying to create a directory in /sdcard programmatically, but it's not working. The code below always outputs directory not created.
boolean success = (new File("/sdcard/map")).mkdir();
if (!success) {
Log.i("directory not created", "directory not created");
} else {
Log.i("directory created", "directory created");
}
There are three things to consider here:
Don't assume that the sd card is mounted at /sdcard (May be true in the default case, but better not to hard code.). You can get the location of sdcard by querying the system:
Environment.getExternalStorageDirectory();
You have to inform Android that your application needs to write to external storage by adding a uses-permission entry in the AndroidManifest.xml file:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
If this directory already exists, then mkdir is going to return false. So check for the existence of the directory, and then try creating it if it does not exist.
In your component, use something like:
File folder = new File(Environment.getExternalStorageDirectory() + "/map");
boolean success = true;
if (!folder.exists()) {
success = folder.mkdir();
}
if (success) {
// Do something on success
} else {
// Do something else on failure
}
I had same issue after I updated my Android phone to 6.0 (API level 23). The following solution works on me. Hopefully it helps you as well.
Please check your android version. If it is >= 6.0 (API level 23), you need to not only include
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
in your AndroidManifest.xml, but also request permission before calling mkdir().
Code snopshot.
public static final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
public int mkFolder(String folderName){ // make a folder under Environment.DIRECTORY_DCIM
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)){
Log.d("myAppName", "Error: external storage is unavailable");
return 0;
}
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
Log.d("myAppName", "Error: external storage is read only.");
return 0;
}
Log.d("myAppName", "External storage is not read only or unavailable");
if (ContextCompat.checkSelfPermission(this, // request permission when it is not granted.
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
Log.d("myAppName", "permission:WRITE_EXTERNAL_STORAGE: NOT granted!");
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),folderName);
int result = 0;
if (folder.exists()) {
Log.d("myAppName","folder exist:"+folder.toString());
result = 2; // folder exist
}else{
try {
if (folder.mkdir()) {
Log.d("myAppName", "folder created:" + folder.toString());
result = 1; // folder created
} else {
Log.d("myAppName", "creat folder fails:" + folder.toString());
result = 0; // creat folder fails
}
}catch (Exception ecp){
ecp.printStackTrace();
}
}
return result;
}
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
More information please read "Requesting Permissions at Run Time"
The correct path to the sdcard is
/mnt/sdcard/
but, as answered before, you shouldn't hardcode it. If you are on Android 2.1 or after, use
getExternalFilesDir(String type)
Otherwise:
Environment.getExternalStorageDirectory()
Read carefully https://developer.android.com/guide/topics/data/data-storage.html#filesExternal
Also, you'll need to use this method or something similar
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Something else is wrong. It may be one of many other states, but all we need
// to know is we can neither read nor write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
then check if you can access the sdcard. As said, read the official documentation.
Another option, maybe you need to use mkdirs instead of mkdir
file.mkdirs()
Creates the directory named by the trailing filename of this file, including the complete directory path required to create this directory.
Restart your Android device. Things started to work for me after I restarted the device.
If this is happening to you with Android 6 and compile target >= 23, don't forget that we are now using runtime permissions. So giving permissions in the manifest is not enough anymore.
use mkdirs() instead of mkdir()..it worked for me :)
File folder = new File(Environment.getExternalStorageDirectory()+"/Saved CGPA");
if(!folder.exists()){
if(folder.mkdirs())
Toast.makeText(this, "New Folder Created", Toast.LENGTH_SHORT).show();
}
File sdCardFile = new File(Environment.getExternalStorageDirectory()+"/Saved CGPA/cgpa.html");
in android api >= 23
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
instead of
<app:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Do you have the right permissions to write to SD card in your manifest ?
Look for WRITE_EXTERNAL_STORAGE at http://developer.android.com/reference/android/Manifest.permission.html
Isn't it already created ? Mkdir returns false if the folder already exists too
mkdir
There are Many Things You Need to worry about
1.If you are using Android Bellow Marshmallow then you have to set permesions in Manifest File.
2. If you are using later Version of Android means from Marshmallow to Oreo now
Either you have to go to the App Info and Set there manually App permission for Storage.
if you want to Set it at Run Time you can do that by below code
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG,"Permission is granted");
return true;
} else {
Log.v(TAG,"Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG,"Permission is granted");
return true;
}
}
File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/FoderName");
if (!f.exists()) {
f.mkdirs();
}
I made the mistake of including both:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
and:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
in the above order. So when I took out the second permission, (READ), the problem went away.
If the error happens with Android 6.0 and API >=23 ; Giving permission in the AndroidManifest.xml is not alone enough.
You have to give runtime permissions, you can refer more here
Runtime Permission
(or)
Google has a new feature on Android Q: filtered view for external storage. A quick fix for that is to add this code in the AndroidManifest.xml file:
<manifest ... >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true">
...
...
</application>
</manifest>
You can read more about it here: https://developer.android.com/training/data-storage/compatibility
Internal Storage vs Seconday Storage
The internal storage is referred to as "external storage" in the API ; not the "secondary storage"
Environment.getExternalStorageDirectory();
As mentioned in the Environment Documentation

Categories

Resources