2

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
Zoe
  • 27,060
  • 21
  • 118
  • 148
James Furrer
  • 73
  • 1
  • 7

4 Answers4

5

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.

James Furrer
  • 73
  • 1
  • 7
2

Use ACTION_SEND instead.

The TOwill come true here too.

Add:

emailIntent.setType("message/rfc822");

You can remove the setData() call;

blackapps
  • 8,011
  • 2
  • 11
  • 25
1

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"

  • Correct, for some reason Gmail doesn't like ```context.filesDir```. Changing it to ```context.externalCacheDir``` takes care of it. – Peter Jan 02 '23 at 14:53
0

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!

Marcel Hofgesang
  • 951
  • 1
  • 15
  • 36