can't delete file from external storage in android programmatically - java

I am trying to delete a file located at the path
/storage/714D-160A/Xender/image/Screenshot_commando.png
What I've done so far:
try{
String d_path = "/storage/714D-160A/Xender/image/Screenshot_commando.png";
File file = new File(d_path);
file.delete();
}catch(Exception e){
e.printStackTrace();
}
and the file is still at its place(Not deleted :( )
Also I've given permission in Manifest file.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.STORAGE" />

public static boolean delete(final Context context, final File file) {
final String where = MediaStore.MediaColumns.DATA + "=?";
final String[] selectionArgs = new String[] {
file.getAbsolutePath()
};
final ContentResolver contentResolver = context.getContentResolver();
final Uri filesUri = MediaStore.Files.getContentUri("external");
contentResolver.delete(filesUri, where, selectionArgs);
if (file.exists()) {
contentResolver.delete(filesUri, where, selectionArgs);
}
return !file.exists();
}

Using ContentResolver to delete media files is wrong and provides many problems for the user.
You can not delete a file on the sd-card simply by deleting its information from the ContentResolver on Android versions greater than Jelly Bean(4.3).
It works only on Android versions prior to KitKat(4.4).
That's why the Android team provided DocumentProvider.
Why contentResolver.delete(...) is wrong?
1. Fills up the sd-card
When you try to delete a media file on the sd-card by the ContentResolver on Android versions greater than 4.3, the actual media file will remain untouched because the contentResolver.delete(...) approach only removes the information (name, date, path ...) of the media and you will end up having unregistered media files on your sd-card which ContentResolver has no idea about their existence anymore and that's why you couldn't see them in your gallery and you think they've been deleted with this approach while they're still there and fill up the sd-card each time the user tries to delete a media file on the sd-card.
2. Media files (Images, videos, gifs ...) will come back to the gallery
There are many apps out there especially gallery and file manager ones that will find these unregistered media files and will add them to the ContentResolver again as of their normal behavior while the user assumes his/her unwanted media files are gone.
Sure no user wants his/her assuming deleted images or videos show up in the middle of a demonstration.
So, what's the correct approach to remove media files on the sd-card?
Well, this has already been answered here with the use of DocumentProvider.

From Android 4.4 onwards, you can't write to SD card files (except in the App directory) using the normal way. You'll have to use the Storage Access Framework using DocumentFile for that.
The following code works for me:
private void deletefile(Uri uri, String filename) {
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, uri);
DocumentFile file = pickedDir.findFile(filename);
if(file.delete())
Log.d("Log ID", "Delete successful");
else
Log.d("Log ID", "Delete unsuccessful");
}
where filename is the name of the file to be deleted and uri is the URI returned by ACTION_OPEN_DOCUMENT_TREE:
private static final int LOCATION_REQUEST = 1;
private void choosePath() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(intent, LOCATION_REQUEST);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == LOCATION_REQUEST && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
if (uri != null) {
/* Got the path uri */
}
}
}
}

Use Environment.getExternalStorageDirectory().getAbsolutePath() instead of hard coding storage path
String baseDir = Environment.getExternalStorageDirectory().getAbsolutePath();
File f = new File(baseDir + "/714D-160A/Xender/image/Screenshot_commando.png");
boolean d = f.delete();

Related

Uploading image from Android to my server (Permission Denied)

I am trying to upload an image selected from gallery to my Springboot server, but when my service try to post the image I get permission denied for the file path. I have added these permissions to my AndroidManifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- permission below just in case, should not be needed I believe -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
I then ask for permission in real time to select the image, and then I want to place it in an inflated view where the user can provide more details about the image, then add it to a report which I will later post.
Since I got this permission trouble I also asked for permission again when I try to submit this Report object containing the images (Uri).
But still I get this error:
Caused by: java.io.FileNotFoundException: /storage/emulated/0/DCIM/Camera/IMG_20200206_120434.jpg (Permission denied)
Every hit I find on this error on google will point to someone who don't ask for this real-time permission, but I even do it once to much I believe.
This is some related snippets of my code:
else if (view.getId() == R.id.stubNewBreedingReportSelectImageButt) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
} else {
getPhotoFromPhone(); // this starts the intent to pick an image
}
}
}
else if (view.getId() == R.id.stubNewBreedingReportSubmitButt) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);
} else {
submitNewBreedingReport();
}
}
}
This is from my onClick(View view) method. The first one works since I am allowed to pick an image from the gallery. The second one should probably not need to check the permissions based on every example I have found of projects uploading images from android.
In my onActivityResult(int requestCode, int resultCode, Intent data) method I inflate this "add image details view". I also store the selected Uri as a private Uri selectedImg in the activity for future use. This all seems to work pretty much fine.
Then when I submit the image (in the submitNewReport() method) I use an ExecutorService (java class) to start a new async thread for the upload. In this Callable<> I get an instance of Springs RestTemplate and try to post the image, but when my restTemplate try to call and fetch the file from my Uri I get the permission denied.
This is the upload method in my apps ImageService:
public Gallery uploadPictureWithInfo(Uri uri, Map<String,Object> imgParams, Context context) {
if (uri.getPath() != null) {
File resourceFile = new File(getPathFromUri(uri,context));
//if (resourceFile.exists()) {
Gallery saved = null;
Map<String,Object> params = new HashMap<>();
params.put(PARAM_FILE, new FileSystemResource(resourceFile));
if (imgParams.get(PARAM_GALLERY_ID) != null || (long) imgParams.get(PARAM_GALLERY_ID) > (long) 0) {
params.put(PARAM_GALLERY_ID, imgParams.get(PARAM_GALLERY_ID));
if (imgParams.get(PARAM_DESCRIPTION) != null) {
params.put(PARAM_DESCRIPTION, imgParams.get(PARAM_DESCRIPTION));
}
if (imgParams.get(PARAM_PHOTOGRAPH) != null) {
params.put(PARAM_PHOTOGRAPH, imgParams.get(PARAM_PHOTOGRAPH));
}
if (imgParams.get(PARAM_USER_ID) != null && (long) imgParams.get(PARAM_USER_ID) > 0) {
params.put(PARAM_USER_ID, imgParams.get(PARAM_USER_ID));
}
HttpEntity requestEntity = new HttpEntity<>(params, AquaDbConfig.getImageHeaders());
ResponseEntity<Gallery> responseEntity =
restTemplate.exchange(AquaDbConfig.getApiUrl() + "/images/uploadImgWithInfo", HttpMethod.POST, requestEntity, Gallery.class);
if (responseEntity.hasBody()) {
saved = responseEntity.getBody();
}
return saved;
}
//}
}
return null;
}
public static String getPathFromUri(Uri uri, Context context) {
String[] filePath = { MediaStore.Images.Media.DATA };
Cursor c = context.getContentResolver().query(uri,filePath, null, null, null);
c.moveToFirst();
int columnIndex = c.getColumnIndex(filePath[0]);
String picturePath = c.getString(columnIndex);
c.close();
return picturePath;
}
I commented out the check for the file.isExist() to get past that test since it wont generate a stack trace otherwise.
So my question is HOW do I get to read the image file when I POST it to the server? I read a little about FileProvider class, but it seems to me that it is used to send files through Intents to new Activites or other Apps. It don't seem to me like it is intended for this because I never leave my Activity exept for picking the image in the gallery. The diffrent steps of creating this ReportedBreeding object is handeled by inflated ViewStubs and not new activites. Also the Uri I use don't refer to any directories I created for my app but rather the users image gallery (external storage).
I also tried to declare my ImageService as a Service in the android manifest, even though I'm not sure we talk about the same kind of service. I then added it this permission but it made no diffrence:
<service
android:name=".service.MyImageFactory"
android:permission="android.permission.READ_EXTERNAL_STORAGE">
</service>
If you know how to get the permission all the way to this RestTemplate POST method (which noone else seems to need in my reviewed examples) or how I can get around this problem, please share! I'm starting to get a little frustrated and stuck. The problem to me is Why do android require yet another permission check and how do I provide it or work around it in my uploadPictureWithInfo(..) method?
Try asking the permission for WRITE_EXTERNAL_STORAGE before getPhotoFromPhone()
For Android 10 this may be the permission issue, there are two solutions for that to handle for now. First method is to permission to manifest Application tag: android:requestLegacyExternalStorage="true"
The other one is to use openFileDescriptor
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
fun ContentResolver.getFileName(fileUri: Uri): String {
var name = ""
val returnCursor = this.query(fileUri, null, null, null, null)
if (returnCursor != null) {
val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
name = returnCursor.getString(nameIndex)
returnCursor.close()
}
return name
}
val file = File(context.cacheDir, getFileName(context.contentResolver, fileUri))
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)
parcelFileDescriptor?.let {
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
val file = File(context.cacheDir, context.contentResolver.getFileName(fileUri))
val outputStream = FileOutputStream(file)
IOUtils.copy(inputStream, outputStream)
}

How to get file path from the content uri for zip file?

I am triggering an intent for selecting a zip file and in onActivityResult, it is providing me a content URI. For example :: (content://com.android.providers.downloads.documents/document/197). The file which is selected is from the download folder. Can any one explain, How to query the content resolver in order to get the file path? Thanks.
Already tried code :-
Cursor cursor = getActivity().getContentResolver()
.query(contentUri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String path=cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
Log.i(TAG, "path Name: " + path);
}
// NOTE :: Here I am getting the file name, But i want the file path.
} finally {
cursor.close();
}
I tried so many methods to retrieve the file path from the selected file in download folder. Since from android Q on-wards google suggested SAF(Storage Access Framework) to get the data from the user. We cannot use Environment.getExternalStoragePublicDirectory() to get the file path from android Q and above.
For android Q and above you can follow this steps.
1) Start the intent from the activity or fragment you want to trigger.
For example :
public void performFileSearch() { //from developer docs
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("application/pdf"); // MIME type for what file you required
startActivityForResult(intent, READ_REQUEST_CODE);
}
2) Then result will come in onActivityResult of your activity or fragment
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) { // from developer docs
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
Log.i(TAG, "Uri: " + uri.toString());
processWithUriToGetPath(uri);
}
}
}
Extracting data from uri(Note: For image,video and audio you can refer developer document.Since didn't find any proper document for file, giving this example).
private void processWithUriToGetPath(Uri contentUri) {
//Use content Resolver to get the input stream that it holds the data and copy that in a temp file of your app file directory for your references
File selectedFile = new File(getActivity().getFilesDir(), "your file name"); //your app file dir or cache dir you can use
InputStream in = getActivity().getContentResolver().openInputStream(contentUri);
OutputStream out = new FileOutputStream(selectedFile);
try {
byte[] buf = new byte[1024];
int len;
if (in != null) {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
in.close();
} catch (IOException ie) {
ie.printStackTrace();
}
//after this you will get file from the selected file and you can do
whatever with your wish.
// Hope it helps...
}

Why is the received URI of the PDF files incorrect? [duplicate]

I am working on an app where I want to be able to export and import some data from the app, on a .txt file.
The minimum API of the app is 21.
The export part works well, but I am having trouble with the import part.
I open the file explorer :
butImportPatient.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
intent.setType("*/*");
startActivityForResult(intent, IMPORTPATIENT_ACTIVITY_REQUEST_CODE);
}
});
This looks like it is working.
But my onActivityResult doesn't work, I didn't find how I can get the file from the Uri.
For now, here is my code :
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == IMPORTPATIENT_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
File file = new File(data.getData().getPath()) ;
String path = file.getAbsolutePath() ;
StringBuilder text = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(path));
String line;
while ((line = br.readLine()) != null) {
text.append(line);
text.append("\n");
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
AlertDialog.Builder builder = new AlertDialog.Builder(this) ;
builder.setMessage(path)
.show() ;
}
}
It is a mix of multiple posts I saw here, but none seems to work.
I get this path :
/document/home:List.txt
It creates FileNotFoundException. How can I get the real path of the file ?
I didn't find how I can get the file from the Uri.
There is no file. ACTION_OPEN_DOCUMENT and ACTION_GET_CONTENT do not open a file. They open a document. That document might be a file. It might not.
That Uri might point to:
A local file on external storage
A local file on internal storage for the other app
A local file on removable storage
A local file that is encrypted and needs to be decrypted on the fly
A stream of bytes held in a BLOB column in a database
A piece of content that needs to be downloaded by the other app first
...and so on
How can I get the real path of the file ?
You don't.
If you wish to only accept files, integrate a file chooser library instead of using ACTION_OPEN_DOCUMENT or
ACTION_GET_CONTENT. Just bear in mind that filesystem access to external storage is limited on Android 10+.
If you use ACTION_GET_CONTENT, and the scheme of the Uri that you get is file, then getPath() will be a filesystem path.
Otherwise, you need to understand that you have no idea where the document is coming from, and stop thinking in terms of "real path of the file". Use ContentResolver and openInputStream() to make a copy of the content to some file that you control, then work with that file.

How can I share multiple files via an Intent?

Here is my code, but this is for a single file solution.
Can I share multiple files & uploads like I do for single files below?
Button btn = (Button)findViewById(R.id.hello);
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_SEND);
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/pic.png";
File file = new File(path);
MimeTypeMap type = MimeTypeMap.getSingleton();
intent.setType(type.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(path)));
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.putExtra(Intent.EXTRA_TEXT, "1111");
startActivity(intent);
}
});
Yes but you'll need to use Intent.ACTION_SEND_MULTIPLE instead of Intent.ACTION_SEND.
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putExtra(Intent.EXTRA_SUBJECT, "Here are some files.");
intent.setType("image/jpeg"); /* This example is sharing jpeg images. */
ArrayList<Uri> files = new ArrayList<Uri>();
for(String path : filesToSend /* List of the files you want to send */) {
File file = new File(path);
Uri uri = Uri.fromFile(file);
files.add(uri);
}
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files);
startActivity(intent);
This could definitely be simplified but I left some lines in so you can break down each step that is needed.
UPDATE: Starting in API 24, sharing file URIs will cause a FileUriExposedException. To remedy this, you can either switch your compileSdkVersion to 23 or lower or you can use content URIs with a FileProvider.
UPDATE (to the update): Google recently announced that new apps and app updates would be required to target one of the latest versions of Android for release to the Play Store. That said, targeting API 23 or lower is no longer a valid option if you plan to release the app to the store. You must go the FileProvider route.
Here is little improved version improvised by MCeley's solution. This could be used to send the heterogeneous file list (like image, document and video at same time), for instance uploading downloaded documents, images at same time.
public static void shareMultiple(List<File> files, Context context){
ArrayList<Uri> uris = new ArrayList<>();
for(File file: files){
uris.add(Uri.fromFile(file));
}
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("*/*");
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
context.startActivity(Intent.createChooser(intent, context.getString(R.string.ids_msg_share)));
}
If you are sharing a file with another applications on devices running KitKat and above, you will need to provide Uri permissions.
This is how I handle multiple file sharing pre and post KitKat:
//All my paths will temporarily be retrieve into this ArrayList
//PathModel is a simple getter/setter
ArrayList<PathModel> pathList;
//All Uri's are retrieved into this ArrayList
ArrayList<Uri> uriArrayList = null;
//This is important since we are sending multiple files
Intent sharingIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
//Used temporarily to get Uri references
Uri shareFileUri;
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
//My paths are stored in SQLite, I retrieve them first
SQLiteHelper helper = new SQLiteHelper(this);
pathList = helper.getAllAttachments(viewholderID);
helper.close();
//Create new instance of the ArrayList where the Uri will be stored
uriArrayList = new ArrayList<>();
//Get all paths from my PathModel
for (PathModel data : pathList) {
//Create a new file for each path
File mFile = new File(data.getPath());
//No need to add Uri permissions for pre-KitKat
shareFileUri = Uri.fromFile(mFile);
//Add Uri's to the Array that holds the Uri's
uriArrayList.add(shareFileUri);
}
} else {
//My paths are stored in SQLite, I retrieve them first
SQLiteHelper helper = new SQLiteHelper(this);
pathList = helper.getAllAttachments(viewholderID);
helper.close();
//Create new instance of the ArrayList where the Uri will be stored
uriArrayList = new ArrayList<>();
//Get all paths from my PathModel
for (PathModel data : pathList) {
//Create a new file for each path
File mFile = new File(data.getPath());
//Now we need to grant Uri permissions (kitKat>)
shareFileUri = FileProvider.getUriForFile(getApplication(), getApplication().getPackageName() + ".provider", mFile);
//Add Uri's to the Array that holds the Uri's
uriArrayList.add(shareFileUri);
}
//Grant read Uri permissions to the intent
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
//I know that the files which will be sent will be one of the following
sharingIntent.setType("application/pdf/*|image|video/*");
//pass the Array that holds the paths to the files
sharingIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriArrayList);
//Start intent by creating a chooser
startActivity(Intent.createChooser(sharingIntent, "Share using"));
In my case the paths were stored in SQLite, but the paths can come from wherever.
/*
manifest file outside the applicationTag write these permissions
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> */
File pictures = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
//Get a top-level public external storage directory for placing files of a particular type.
// This is where the user will typically place and manage their own files,
// so you should be careful about what you put here to ensure you don't
// erase their files or get in the way of their own organization...
// pulled from Standard directory in which to place pictures that are available to the user to the File object
String[] listOfPictures = pictures.list();
//Returns an array of strings with the file names in the directory represented by this file. The result is null if this file is not a directory.
Uri uri=null;
ArrayList<Uri> arrayList = new ArrayList<>();
if (listOfPictures!=null) {
for (String name : listOfPictures) {
uri = Uri.parse("file://" + pictures.toString() + "/" + name );
arrayList.add(uri);
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putExtra(Intent.EXTRA_STREAM, arrayList);
//A content: URI holding a stream of data associated with the Intent, used with ACTION_SEND to supply the data being sent.
intent.setType("image/*"); //any kind of images can support.
chooser = Intent.createChooser(intent, "Send Multiple Images");//choosers title
startActivity(chooser);
}

Email from internal storage

On my application I write a file to the internal storage as covered on android developer. Then later on I wish to email the file I wrote into the internal storage. Here is my code and the error I am getting, any help will be appreciated.
FileOutputStream fos = openFileOutput(xmlFilename, MODE_PRIVATE);
fos.write(xml.getBytes());
fos.close();
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
...
Uri uri = Uri.fromFile(new File(xmlFilename));
intent.putExtra(android.content.Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Send eMail.."));
And the error is
file:// attachment path must point to file://mnt/sdcard. Ignoring attachment file://...
I think you may have found a bug (or at least unnecessary limitation) in the android Gmail client. I was able to work around it, but it strikes me as too implementation specific, and would need a little more work to be portable:
First CommonsWare is very much correct about needing to make the file world readable:
fos = openFileOutput(xmlFilename, MODE_WORLD_READABLE);
Next, we need to work around Gmail's insistence on the /mnt/sdcard (or implementation specific equivalent?) path:
Uri uri = Uri.fromFile(new File("/mnt/sdcard/../.."+getFilesDir()+"/"+xmlFilename));
At least on my modified Gingerbread device, this is letting me Gmail an attachment from private storage to myself, and see the contents using the preview button when I receive it. But I don't feel very "good" about having to do this to make it work, and who knows what would happen with another version of Gmail or another email client or a phone which mounts the external storage elsewhere.
I have been struggling with this issue lately and I would like to share the solution I found, using FileProvider, from the support library. its an extension of Content Provider that solve this problem well without work-around, and its not too-much work.
As explained in the link, to activate the content provider:
in your manifest, write:
<application
....
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.youdomain.yourapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
...
the meta data should indicate an xml file in res/xml folder (I named it file_paths.xml):
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="" name="document"/>
</paths>
the path is empty when you use the internal files folder, but if for more general location (we are now talking about the internal storage path) you should use other paths. the name you write will be used for the url that the content provider with give to the file.
and now, you can generate a new, world readable url simply by using:
Uri contentUri = FileProvider.getUriForFile(context, "com.yourdomain.yourapp.fileprovider", file);
on any file from a path in the res/xml/file_paths.xml metadata.
and now just use:
Intent mailIntent = new Intent(Intent.ACTION_SEND);
mailIntent.setType("message/rfc822");
mailIntent.putExtra(Intent.EXTRA_EMAIL, recipients);
mailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
mailIntent.putExtra(Intent.EXTRA_TEXT, body);
mailIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
try {
startActivity(Intent.createChooser(mailIntent, "Send email.."));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(this, R.string.Message_No_Email_Service, Toast.LENGTH_SHORT).show();
}
you don't need to give a permission, you do it automatically when you attach the url to the file.
and you don't need to make your file MODE_WORLD_READABLE, this mode is now deprecated, make it MODE_PRIVATE, the content provider creates new url for the same file which is accessible by other applications.
I should note that I only tested it on an emulator with Gmail.
Chris Stratton proposed good workaround. However it fails on a lot of devices. You should not hardcode /mnt/sdcard path. You better compute it:
String sdCard = Environment.getExternalStorageDirectory().getAbsolutePath();
Uri uri = Uri.fromFile(new File(sdCard +
new String(new char[sdCard.replaceAll("[^/]", "").length()])
.replace("\0", "/..") + getFilesDir() + "/" + xmlFilename));
Taking into account recommendations from here: http://developer.android.com/reference/android/content/Context.html#MODE_WORLD_READABLE, since API 17 we're encouraged to use ContentProviders etc.
Thanks to that guy and his post http://stephendnicholas.com/archives/974 we have a solution:
public class CachedFileProvider extends ContentProvider {
public static final String AUTHORITY = "com.yourpackage.gmailattach.provider";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
switch (uriMatcher.match(uri)) {
case 1:// If it returns 1 - then it matches the Uri defined in onCreate
String fileLocation = AppCore.context().getCacheDir() + File.separator + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
default:// Otherwise unrecognised Uri
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
#Override public int update(Uri uri, ContentValues contentvalues, String s, String[] as) { return 0; }
#Override public int delete(Uri uri, String s, String[] as) { return 0; }
#Override public Uri insert(Uri uri, ContentValues contentvalues) { return null; }
#Override public String getType(Uri uri) { return null; }
#Override public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) { return null; }
}
Than create file in Internal cache:
File tempDir = getContext().getCacheDir();
File tempFile = File.createTempFile("your_file", ".txt", tempDir);
fout = new FileOutputStream(tempFile);
fout.write(bytes);
fout.close();
Setup Intent:
...
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + tempFile.getName()));
And register Content provider in AndroidManifest file:
<provider android:name="CachedFileProvider" android:authorities="com.yourpackage.gmailattach.provider"></provider>
File.setReadable(true, false);
worked for me.
The error is enough specific: you should use file from external storage in order to make an attachment.
If you are going to use internal storage, try to use the exact storage path:
Uri uri = Uri.fromFile(new File(context.getFilesDir() + File.separator + xmlFilename));
or additionally keep changing the file name in the debugger and just call "new File(blah).exists()" on each of the file to see quickly what exact name is your file
it could also be an actual device implementation problem specific to your device. have you tried using other devices/emulator?

Categories