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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With