I've been searching for many topics about android file writing, yet most of them wanted to write files to android internal storage. Others who wanted to write files on external SD card didn't success at all. My case is quite similar but I think that writing files to external USB is a totally different case.
I am using Samsung galaxy Note II running stock TouchWiz 4.4.2 [not rooted]. My phone supports micro-USB-OTG and I can mount my USB as rwxrwx--x without rooting. The complete path of my USB is /storage/UsbDriveA.
I've tried to use Environment.getExternalStorageDirectory() to get the path or use the path (mentioned above) directly but neither of them succeed. The first one returns internal storage path and the second one returns an error with "permission denied". I have already put the
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
in Android Manifest so I wondered why my code didn't work.
Moreover, I can write anything to my USB using Root Browser (use it without root) and Simple Browser thus I believe that there's a way to do that.
Here's my code:
File file = new File(path.getAbsolutePath(), "test.txt");
// File file = new File("/storage/extSdCard","test.txt");
err = false;
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.print(get);
pw.flush();
pw.close();
f.close();
}catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "writing error",Toast.LENGTH_LONG).show();
err = true;
}
Log.i("File Path:", file.getPath());
From android 4.4, you can use Storage Access Framework to access to removable media (see https://commonsware.com/blog/2014/04/09/storage-situation-removable-storage.html).
For example, I tried with success to copy a pdf file from local memory to removable memory connected by OTG adapter. The only limitation: the user has to choose a destination folder.
1) call Intent.ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, file.getName());
startActivityForResult(intent, REQUEST_CODE);
2) intercept the return intent
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == REQUEST_CODE) {
if (resultCode != RESULT_OK) return;
copyFile(fileToCopy, data.getData());
}
}
3) use the ContentResolver to open the outputStream and use it to copy the file
private void copyFile(File src, Uri destUri) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(getContentResolver().openOutputStream(destUri));
byte[] buf = new byte[1024];
bis.read(buf);
do {
bos.write(buf);
} while(bis.read(buf) != -1);
} catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) bis.close();
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
From https://source.android.com/devices/storage/
Starting in Android 4.4, ...
The WRITE_EXTERNAL_STORAGE permission must only grant write access to
the primary external storage on a device. Apps must not be allowed to
write to secondary external storage devices, except in their
package-specific directories as allowed by synthesized permissions.
Restricting writes in this way ensures the system can clean up files
when applications are uninstalled.
So, starting from Android 4.4 in devices with multiple external storages you will be able to write only on the primary external storage. Take into account that External Storage does not mean only "real external" devices. It is defined as follows (from the External Storage reference)
External storage can be provided by physical media (such as an SD
card), or by exposing a portion of internal storage through an
emulation layer.
Anyway there is a workaround to write to secondary external storage using the media content provider. Take a look at http://forum.xda-developers.com/showthread.php?t=2634840
I have used it on a project of mine, but as the author says, it's far from the ideal solution, and it is not guaranteed to work on coming Android versions, so you must not let all your app to rely on this workaround.
Related
I have implemented a method for users to save/export files created by my app to any location using the Storage Access Framework. The architecture is:
Use Intent.ACTION_OPEN_DOCUMENT_TREE to have the user select a directory
Use DocumentsContract to create a file in that directory
Write the data out to this file
The file is a custom file extension containing custom data, so not a standard file type or mime type.
I am using a Samsung Galaxy S7 with an SD card inserted. So it does not support Adoptable Storage. For testing/developing, I am using the My Files system app to open files and a Windows PC with MTP to pull files from the device.
This architecture/code works very well in the following circumstances:
Any internal storage location
Secondary external storage, unencrypted (removable SD card)
If the SD card is encrypted, I start running into strange issues.
Issue 1: "My Files" system app sees the file as having size 0 bytes and will not open it.
Issue 2: Windows PC also sees the file as having size 0 bytes and will not copy it to my PC over MTP.
Strangely, ES File Explorer can see the file and open it. DropBox can see the file and upload it. Issue 1 and 2 above are not present if the SD card is NOT encrypted.
So what is going on here? I have tried a lot of things to troubleshoot. My theory is that there's something going on with handling MIME types and Content URIs that behaves differently with encryption on or off - specifically concerning My Files and MTP. Why would ES File Explorer have no issues seeing this file and opening it?
Here are relevant code snippets:
private void selectStoragePathExtended() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY) {
if (resultCode == Activity.RESULT_OK) {
mLocalStorageUri = DocumentsContract.buildDocumentUriUsingTree(data.getData(), DocumentsContract.getTreeDocumentId(data.getData()));
}
}
}
// This will get called with mLocalStorageUri and a File stored in the app's "ExternalFilesDir"
public static void copyFile(ContentResolver cr, File sourceFile, Uri destFolderUri) throws IOException {
if (!sourceFile.exists()) {
return;
}
FileChannel source = null;
FileChannel destination = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
try {
source = new FileInputStream(sourceFile).getChannel();
Uri destUri = DocumentsContract.createDocument(cr, destFolderUri, "*/*", sourceFile.getName());
ParcelFileDescriptor pfd = cr.openFileDescriptor(destUri, "w");
destination = new FileOutputStream(pfd.getFileDescriptor()).getChannel();
if (source.size() > FILE_COPY_MAX_BLOCK) {
// Transfer file in 128MB blocks
long position = 0;
while (destination.transferFrom(source, position, FILE_COPY_MAX_BLOCK) > 0) {
position += FILE_COPY_MAX_BLOCK;
}
} else {
long bytesCopied = destination.transferFrom(source, 0, source.size());
if (bytesCopied != source.size()) {
String errorMsg = String.format("Error: only %d out of %d bytes copied", bytesCopied, source.size());
throw new IOException(errorMsg);
}
}
destination.close();
pfd.close();
}
finally {
if(source != null) {
source.close();
}
if(destination != null) {
destination.close();
}
}
}
}
Yes I am using a non-standard file copy method with FileChannel, but I have tested with basic FileOutputStreams and other methods with same results. Also, remember, behavior is normal and working with SD card encryption off. And it actually does work with encryption on, but neither My Files nor Windows MTP can read the file. Only 3rd party tools like ES File Explorer and DropBox.
Any ideas? Gotta be a mime type/URI issue?
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)
I create images in my app and want to share these social networks (facebook), mail apps (gmail), and other apps that can "receive" images.
The origin of the problem (I think) is that I don't want to use the external storage as a base for my images. I want to either use my data folder or my cache folder since neither of these require any permission to access.
The code which I use to write my image to file (and I specify the MODE_WORLD_READABLE so that other apps can read them):
FileOutputStream fos = null;
try {
fos = context.openFileOutput("image.jpg", Context.MODE_WORLD_READABLE);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} finally {
if (fos != null)
fos.close();
}
And this is the code where I share the image:
File internalFile = context.getFileStreamPath("image.jpg");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(internalFile));
intent.setType("image/jpeg");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, "share"));
This solution is very easy and works fine for apps like facebook but not for example gmail which failes with:
file:// attachment paths must point to file:///mnt/sdcard
There are a number of "hacks" (see below) to get it to work with gmail but I leaves me asking myself if there is an even better way to share images that works without hacks, something I overlooked. So, to the questions:
What is the best way to share images? (external storage?)
Is there any more apps that (mis-)behave just like gmail? (I have seen some trouble with google+)
If there is no other way: Can I write special intents for sharing to specific apps. I have a default way of sharing and override it when the user selects an app on my watch list?
Hacks
Using a path-hack by simply pointing the Uri to:
file:///mnt/sdcard/../../my/package/name/...
This solution doesn't feel right.
Using a ContentProvider as described here. But quoted from the link:
Warning: the method described in the post works well for Gmail, but apparently has some issues with other ACTION_SEND handlers (e.g. the MMS composer).
(Issue: It crashes the MMS composer)
Did you try ParecelableFileDescriptor?
http://developer.android.com/reference/android/os/ParcelFileDescriptor.html
Create with
static ParcelFileDescriptor open(File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener)
Create a new ParcelFileDescriptor accessing a given file.
static ParcelFileDescriptor open(File file, int mode)
Create a new ParcelFileDescriptor accessing a given file.
Receiver side like this:
Returning an Input Stream from Parcel File Descriptor using Androids DownloadManager
You should to make 3 steps.
Take picture.
public Bitmap takeScreenshot() {
View rootView = findViewById(android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
return rootView.getDrawingCache();
}
Save picture.
public String saveBitmap(Bitmap bitmap) {
File imagePath = new File(Environment.getExternalStorageDirectory() + “/screenshot.png”);
FileOutputStream fos;
try {
fos = new FileOutputStream(imagePath);
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
Log.e(“GREC”, e.getMessage(), e);
} catch (IOException e) {
Log.e(“GREC”, e.getMessage(), e);
}
return imagePath.getAbsolutePath();
}
Share to social network.
I'm faced with the well-known problem of obtaining the path of an external SD card mounted on some Android devices. (see this question for understanding what I mean)
I've thought to solve the problem by reading the content of /etc/vold.fstab, then taking just lines representing partitions, but I don't have a device for doing tests.
What I want to do is to read that file, ignore the row which refers to the address returned by Environment.getExternalStorageDirectory(), and take the other row (if present).
What I don't know (and I don't have the possibility to test it) is: are there cases in which I can have other lines which are not the external SD card? The SD card, if present, appears on the file vold.fstab?
edit:
The answer is: YES. Read the accepted answer.
What is wrong with this?
Environment.getExternalStoreDirectory()
Why are you ignoring this when it's the SD Card?
OK - In the case of devices with /sdcard (Internal) and an external SD card (??) you could always scan the fstab file and look for "sdhci" which is the SD Host Controller bridge driver.
Something like:
dev_mount sdcard /mnt/external_sdcard auto /devices/platform/sdhci.2/mmc_host/mmc2
Then just parse as necessary.
Why the "necessity" to find the actual SD card though when it's not actually treated as such by the OS? (Won't be mounted as mass storage)
Is your application only available for devices where this is the case? What is wrong with using whatever Android believes is the SD storage space?
I use the following code to first detect wether the sdCard exists and then run the relevent code:
Detecting whether SD card exists:
Boolean isSDPresent = android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
if(isSDPresent)
{
// file path = "/mnt/sdcard/Android/data/PACKAGE_NAME/..."
}
else
{
// file path = "/data/data/PACKAGE_NAME/..."
}
Think this is what you are after?
This could be the right solution. Read it from /etc/vold.fstab, which lists all the partitions currently mounted on a Linux system (Android included)
String getExternalSdcardDirectory() {
FileInputStream fis = null;
try {
fis = new FileInputStream(new File("/etc/vold.fstab"));
} catch (FileNotFoundException e) {
return null; // should never be reached
}
try {
byte[] buffer = new byte[4096];
int n=0;
String file = "";
while ((n=fis.read(buffer, 0, 4096))>0) {
file += new String(buffer, 0, n);
}
fis.close();
String[] rows = file.split("\n");
for (String row: rows) {
String trimmedRow = row.trim();
if (trimmedRow.startsWith("#") || trimmedRow.equals(""))
continue;
else if (trimmedRow.equals(Environment.getExternalStorageDirectory().getAbsolutePath()))
continue;
else
return trimmedRow.split(" ")[2];
}
} catch (IOException e) {
// nothing
}
return null;
}
While writing file in External SD card I am getting an error EACCESS permission denied. I have set the permission
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
But the when I read the file I am successfully able to read it but not able to write the file. The code that I am using for writing the file in SD card is:
String path="mnt/extsd/Test";
try{
File myFile = new File(path, "Hello.txt"); //device.txt
myFile.createNewFile();
FileOutputStream fOut = new FileOutputStream(myFile);
OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
myOutWriter.append(txtData.getText());
myOutWriter.close();
fOut.close();
Toast.makeText(getBaseContext(),"Done writing SD "+myFile.getPath(),Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(getBaseContext(), e.getMessage(),Toast.LENGTH_SHORT).show();
System.out.println("Hello"+e.getMessage());
}
}
The path for the external storage card is mnt/extsd/. Thats why I am not able to use Environment.getExternalStorageDirectory().getAbsolutePath() which is giving me a path mnt/sdcard and this path is for internal storage path in my tablet. Please suggest why this is so n how can I resolve this
As I remember Android got a partial multi-storage support since Honeycomb, and the primary storage (the one you get from Environment.getExternalStorageDirectory, usually part of the internal eMMC card) is still protected by the permission WRITE_EXTERNAL_STORAGE, but the secondary storages (like the real removable SD card) are protected by a new permission android.permission.WRITE_MEDIA_STORAGE, and the protection level is signatureOrSystem, see also the discussion in this article.
If this is the case then it seems impossible for an normal app to write anything to the real sdcard without a platform signature...
From API level 19, Google has added API.
Context.getExternalFilesDirs()
Context.getExternalCacheDirs()
Context.getObbDirs()
Apps must not be allowed to write to secondary external storage devices, except in their package-specific directories as allowed by synthesized permissions. Restricting writes in this way ensures the system can clean up files when applications are uninstalled.
Following is approach to get application specific directory on external SD card with absolute paths.
Context _context = this.getApplicationContext();
File fileList2[] = _context.getExternalFilesDirs(Environment.DIRECTORY_DOWNLOADS);
if(fileList2.length == 1) {
Log.d(TAG, "external device is not mounted.");
return;
} else {
Log.d(TAG, "external device is mounted.");
File extFile = fileList2[1];
String absPath = extFile.getAbsolutePath();
Log.d(TAG, "external device download : "+absPath);
appPath = absPath.split("Download")[0];
Log.d(TAG, "external device app path: "+appPath);
File file = new File(appPath, "DemoFile.png");
try {
// Very simple code to copy a picture from the application's
// resource into the external file. Note that this code does
// no error checking, and assumes the picture is small (does not
// try to copy it in chunks). Note that if external storage is
// not currently mounted this will silently fail.
InputStream is = getResources().openRawResource(R.drawable.ic_launcher);
Log.d(TAG, "file bytes : "+is.available());
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.d("ExternalStorage", "Error writing " + file, e);
}
}
Log output from above looks like:
context.getExternalFilesDirs() : /storage/extSdCard/Android/data/com.example.remote.services/files/Download
external device is mounted.
external device download : /storage/extSdCard/Android/data/com.example.remote.services/files/Download
external device app path: /storage/extSdCard/Android/data/com.example.remote.services/files/
I solved this problem by removing the android:maxSdkVersion="18" in uses-permission
in manifest file.
I.e. use this:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
instead of:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
Check if user is having external storage permission or not. If not then use cache dir for saving the file.
final boolean extStoragePermission = ContextCompat.checkSelfPermission(
context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED;
if (extStoragePermission &&
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) {
parentFile = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
else{
parentFile = new File(context.getCacheDir(), Environment.DIRECTORY_PICTURES);
}