I've got an intent that uses FileProvider to read from internal file storage for my app to send a file via email (or similar apps). The code works great everywhere apparently except for Gmail, which strangely adds a version of the provider path itself to the list of addressees of the email.
This is the code generating the intent:
public static Intent getSendIntent(Uri uri) {
final Intent i = new Intent(Intent.ACTION_SEND);
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.setDataAndType(uri, "message/rfc822");
i.putExtra(Intent.EXTRA_EMAIL, new String[] { "my#email.com" });
i.putExtra(Intent.EXTRA_SUBJECT, "Export");
i.putExtra(Intent.EXTRA_TEXT, "See the attached...");
i.putExtra(Intent.EXTRA_STREAM, uri);
return i;
}
This is the code to start the activity:
File file = new File(getFilesDir(), filename);
Uri uri = FileProvider.getUriForFile(MainActivity.this, "com.example.myapplication.provider", file);
Intent i = Utils.getSendIntent(uri);
try {
startActivity(Intent.createChooser(i, "Send file..."));
} catch (Exception e) {
Log.d(TAG, "failed to start send activity: " + e.getMessage());
Toast.makeText(MainActivity.this, "No suitable activity found for export.", Toast.LENGTH_SHORT).show();
}
Works well in Slack, Evernote, etc. but in Gmail in addition to the email address I've provided, another addressee in this kind of format is added:
//com.example.myapplication.provider/my_files/filefile.csv
which prevents the email from sending until it's manually removed from the message. Everything else about the message is as expected.
Any clue how to prevent this?
The following Captain Obvious solution resolved the problem.
Replace:
i.setDataAndType(uri, "message/rfc822");
with:
i.setType("message/rfc822");
since, as #CommonsWare's question inferred, the file content isn't itself in rfc822 format.
Related
Right now for sdk 30, I am successfully able to send and attach a json file to some email client when using ACTION_SEND. However there are two issues that I noticed when using ACTION_SEND:
it shows ALL apps, not just email apps, which is what I want
it does NOT show the intent with title/text for the user to choose an email app. I do NOT just want to use a Toast message
I think this can be solved using ACTION_SENDTO instead, but then I won't be able to attach my file, so that actually won't work.
With this said, I would like to solve either point 1 or 2 (preferably point 1), if not both.
Here is what I have:
private void sendToEmail(String emailAddress, String filePath) {
File file = new File(filePath);
file.setReadOnly();
String[] to = { emailAddress };
Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
grantUriPermission("com.my.package", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent intent = new Intent(Intent.ACTION_SEND);
//intent.setData(Uri.parse("mailto:")); // this does NOT work for the above action type
intent.putExtra(Intent.EXTRA_EMAIL, to);
intent.putExtra(Intent.EXTRA_SUBJECT, "Your backup file");
intent.putExtra(Intent.EXTRA_TEXT, "Your data for your backup is attached to the file " + BACKUP_NAME + ".");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("message/rfc822");
// this does NOT create the below title for ACTION_SEND, as per documentation for createChooser()
Intent chooser = Intent.createChooser(intent, "Pick an email app to send attachment: " + BACKUP_NAME);
if (chooser.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
}
So I've been banging my head against the wall for the past day trying to figure out why a file won't attach to an email. Every time the app runs, I get a little toast message that pops up saying "Couldn't Attach File". The To and Subject fields fill just as I expect. First question is, how can I find out further information behind this error? This message is thrown from the GMail app and not my own program. It would certainly point me in the correct direction so I can debug further on my own if I had a reason for this error. I have included info below that may be relevant. The file size is 78.5kb and I can verify that the file exists and has the correct content I'd like to attach. Permissions on the file are rw for owner and group according to the file explorer.
Something interesting that I discovered while typing up this post; when using the file explorer for adding an attachment in the gmail app, the Android/data directory is not an option! It shows up when you are using the file explorer outside of the gmail app though. So I'm thinking this may be a permissions problem then? It cannot access this folder. If that's the case, what would be a recommended location to store this file? Ideally it would be some sort of cache location or temporary file location in this instance.
I have tried adding attachments in the Android Outlook app instead of GMail and the file does get attached.
Trying to run on Android 11 API 30 using the Pixel 4 Emulator.
Code for email intent:
protected void sendEmail(File f){
final String[] TO = { "foo#bar.com" };
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse("mailto:"));
emailIntent.putExtra(Intent.EXTRA_EMAIL, TO);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, f.getName().replaceAll("(?i).pdf", ""));
if (!f.exists() || !f.canRead()) {
Toast.makeText(this, "Attachment Error", Toast.LENGTH_SHORT).show();
finish();
return;
}
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), BuildConfig.APPLICATION_ID, f);
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
try {
startActivity(emailIntent);
finish();
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(MainActivity.this,
"There is no email client installed.", Toast.LENGTH_SHORT).show();
}
AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
provider_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-cache-path name="external_cache" path="."/>
</paths>
Value of f:
/storage/emulated/0/Android/data/com.bar.foo/cache/Form2020-12-27.pdf
Value of uri path:
content://com.bar.foo/external_cache/Form2020-12-27.pdf
After some further futzing, I figure out "a solution". It might not be applicable in some situations, but the result I am after it just so happens to work. It has something to do with the intent data and type. I've change the intent setup to the following, leaving all other items untouched:
protected void sendEmail(File f){
final String[] TO = { "foo#bar.com" };
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.putExtra(Intent.EXTRA_EMAIL, TO);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, f.getName().replaceAll("(?i).pdf", ""));
if (!f.exists() || !f.canRead()) {
Toast.makeText(this, "Attachment Error", Toast.LENGTH_SHORT).show();
finish();
return;
}
Uri uri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID, f);
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
emailIntent.setDataAndType(uri, getContentResolver().getType(uri));
emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
try {
startActivity(emailIntent);
finish();
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(MainActivity.this,
"There is no email client installed.", Toast.LENGTH_SHORT).show();
}
}
I changed the Intent action from ACTION_SENDTO to ACTION_SEND
This alone did not fix the problem.
Instead of setting the intent data and type manually with "mailto:", i set the data and type from the uri.
If a default app is not set, a picker will pop up asking the user which relevant apps to use with the file. This shows more than just email clients though, which may not be desirable for some applications. It's not really the original intent of my design, however it will work considering that my users may want to atttach the form to non-email clients such as MS Teams or perhaps a cloud storage client. Clicking on GMail will attach the file to the email and set the subject and recipients as it is intended to.
Use ACTION_SEND instead.
The TOwill come true here too.
Add:
emailIntent.setType("message/rfc822");
You can remove the setData() call;
My problem was saving PDF File to external file path (Not external cache path). I guess either Gmail can't get files from there or you can't share file from external files directory. Changing file save/retrieve directory to external cache solved my problem.
By the way, my intent-type is "application/pdf". I didn't use "message/rfc822"
I faced some issues due to support for different devices. This is the solution I came up with:
Intent intent = new Intent(Intent.ACTION_SEND);
Uri data = Uri.parse("mailto:" + Uri.encode(mailAddress)
+ "?subject=" + Uri.encode(subject)
+ "&body=" + Uri.encode(message));
intent.setData(data);
// yes this is duplicate intended code but this ensures more compatibility for different devices
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailAddress});
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, message);
String filePath = BaseApplication.getAppContext().getFilesDir().toString() + "/" + FILENAME;
File attachment = new File(filePath);
attachment.setReadable(true, false);
try {
Uri u = FileProvider.getUriForFile(this,
Constants.FILE_PROVIDER_AUTHORITY,
attachment);
intent.putExtra(Intent.EXTRA_STREAM, u);
intent.setType("message/rfc822");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error while providing attachment: " + attachment, e);
}
startActivity(intent);
Additionally one will need a FileProvider, like already mentioned in the question.
Eventually a bit late for the questioner, but hopefully it will help someone else!
So I've been looking all around and can't seem to figure it out, or maybe because I'm inside the emulator?
Basically I'm trying to download a file, and then show a app chooser so the user can freely choose which ever app to open it in.
One thing I'm not sure about is, how do you do a wild card mime type for the intent? I mean for example the downloaded file could be shared and opened by the mail client as an attachment, so really it should support anything.
For the purpose of brevity, the download code works and downloads, so assume the download is completed and the file is in the cache directory:
Intent install = new Intent(Intent.ACTION_VIEW);
// Create intent to show chooser
Intent chooser = Intent.createChooser(install, "Open in...");
// Verify the intent will resolve to at least one activity
if (install.resolveActivity(_progressDialog.getContext().getPackageManager()) != null) {
_progressDialog.getContext().startActivity(chooser);
}
Am I doing anything wrong?
I believe you need to provide the mime type as well e.g. with
intent.setDataAndType(Uri, mimetype);
where mimetype is like "text/plain". You can extract it from file extension, header using special method or lookup table (I don't recall how exactly it is done now).
But that's not working either at least in the emulator.
You can use this below intent to attach thefile & send email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"email#example.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "subject here");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
File root = Environment.getExternalStorageDirectory();
File file = new File(root, xmlFilename);
if (!file.exists() || !file.canRead()) {
Toast.makeText(this, "Attachment Error", Toast.LENGTH_SHORT).show();
finish();
return;
}
Uri uri = Uri.parse("file://" + file);
intent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Send email..."));
I am trying to share an audio file saved by my application. The file is added to the media store so all apps can access it.
Intent mediaScannerIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri fileContentUri = Uri.fromFile(finalFile);
mediaScannerIntent.setData(fileContentUri);
this.sendBroadcast(mediaScannerIntent);
I use Intent.ACTION_SEND to share the files to other apps:
public void shareRecording(View view) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.setType("audio/mp3");
i.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///" + recording.getFilePath()));
try {
startActivity(Intent.createChooser(i, "Share " + recording.getName()));
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(this, "There are no app installed to share your audio file.", Toast.LENGTH_SHORT).show();
}
}
All working good with all apps like GMail, Yahoo mail, Whatsapp, ...
except BBM, it gives access denied.
What do I need to do to make it work on BBM ?
Thanks for any help.
UPDATE:
I used
Uri fileUri = Uri.parse("content://" + recording.getFilePath());
instead of
Uri fileUri = Uri.parse("file:///" + recording.getFilePath());
and it worked on BBM but not othe apps
So what is the difference between havin the URI parsed with: "file:///" and "content://" ?
And how can I used this to get the share working for all apps ?
The solution is to use the actual file to initialize the Uri object.
File recordingFile = new File(recording.getFilePath());
Uri fileUri = Uri.fromFile(recordingFile);
This worked with all the apps that can share a file.
I'm developing and android app, and at certain point i want to allow the user to send a .csv file by email.
I've seen a lot of tutorials in the web, and all of them have the same code, the same code i've tryed many times. The thing is, the send email activity starts, the attachment appears in the email, but when it is sent, the file or goes empty to the destination, or doesn't appear in the destination at all.
Here is some piece of code:
File file = null;
File dir = ProjectViewerResume.this.getCacheDir();
if (dir.canWrite()){
file = new File(dir, ProjectViewerResume.this.mProject.getName()+".csv");
FileOutputStream out = new FileOutputStream(file);
out.write(csv.toString().getBytes());
out.close();
}
Uri u1 = null;
u1 = Uri.fromFile(file);
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Project Resume: "+
ProjectViewerResume.this.mProject.getName());
sendIntent.putExtra(Intent.EXTRA_STREAM, u1);
sendIntent.setType("text/csv");
startActivity(sendIntent);
The csv variable has file content in csv format.
I've omited the try..catch so the code seems cleaner..
Thks in advance
You can try this....
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Subject of Email");
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("/mnt/sdcard/file.csv"));
sendIntent.putExtra(Intent.EXTRA_TEXT, "Enjoy the mail");
startActivity(Intent.createChooser(sendIntent, "Email:"));
If some problem occur in attaching file try following path.
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///sdcard/file.csv"));
I have the exact same problem. So not to duplicate the same post I want to show you my code, which is slightly different from yours, but still sends an email with a 0KB attachment.
I print 2 messages to the Log:
The path is printed (I edited the package name)
The size of the file is not 0KB !!!
05-11 11:32:58.133: W/Log Viewer(432): Path is /data/data/<package>/files/LogCat.log.gzip
05-11 11:32:58.192: W/Log Viewer(432): The file is 504 bytes
I stopped inside the code with the Eclipse debugger and inspected the uriLog: it is not null and contains the right path.
The code is:
public void run() {
Uri uriLog = null;
try {
FileOutputStream out = openFileOutput("LogCat.log.gzip", Context.MODE_PRIVATE);
out.write(gzip(dump().getBytes()));
out.close();
File gzipFile = new File(getFilesDir(), "LogCat.log.gzip");
//uriLog = Uri.parse("file://" + getFilesDir() + "/LogCat.log.gzip");
uriLog = Uri.fromFile(gzipFile);
Log.w(TAG,"Path is " + uriLog.getPath());
Log.w(TAG,"The file is " + gzipFile.length() + " bytes");
}
catch (IOException e) {
e.printStackTrace();
}
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Android Log");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, dump());
emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, uriLog);
try {
String str = getResources().getString(R.string.send_prompt);
Intent chooser = Intent.createChooser(emailIntent, str);
startActivity(chooser);
}
catch (Exception e) {
e.printStackTrace();
}
}
}).start();
Things I tried:
I created the uri by parse (see the commented line
I also put the data inside the email body to check it exists --> it arrives in the email
I also tried to put the gziped data in the email body to check it exists --> it arrived in the email
EDIT:
Tried to send a pre-existing file in the emulator, from /mnt/system/app/CustomLocale.apk 23745 bytes
The path from uriLog.getPath() was printed /apk/CustomLocale.apk and the mail still arrived with an attachment named CustomLocale.apk and 0KB size
The only thing it occurs to me that is failing, is that putextra(intent.EXTRA_STREAM,uri) is based on
public Intent putExtra (String name, Parcelable value)
and uris implement the parcelable interface. But for some reason, which I will investigate, maybe the call to writeToParcel(Parcel out, int flags) is not writing the file data out; that's why it is 0KB in the attachment.
This code works now for me.
Using createTempFile instead of openFileOutput. I still don't know what was wrong with the former approach.
It attaches a file named LogCat.log.gzip which is not empty this time and it has the data I put insde.
public void run() {
Uri uriLog = null;
try {
File gzipFile = File.createTempFile("LogCat.log", ".gzip");
FileOutputStream out = new FileOutputStream(gzipFile);
out.write(gzip(dump().getBytes()));
out.close();
uriLog = Uri.fromFile(gzipFile);
}
catch (IOException e) {
e.printStackTrace();
}
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Android Log");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, dump());
emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, uriLog);
try {
String str = getResources().getString(R.string.send_prompt);
Intent chooser = Intent.createChooser(emailIntent, str);
startActivity(chooser);
}
catch (Exception e) {
e.printStackTrace();
}
}
}).start();
And don't forget to add this line to your manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />