8

At first you're going to think "Wait, this question is a duplicate!". Read on.

I'm trying to use the Intent ACTION_SENDTO (with an Email URI as data) in order to have just email apps respond.

(Using ACTION_SEND launches a standard "SEND" chooser with no data URI meaning that non email apps, such as Google Drive, also respond).

My problem is that the attachment works with ACTION_SEND on all devices, however - when using ACTION_SENDTO only some devices correctly attach the files. Nexus 7 works but Samsung Galaxy Tab and Acer Iconia don't.

You can see below the different methods side by side:

    String email    = getActivity().getResources().getString(R.string.supportEmail);
    String subject  = getActivity().getResources().getString(R.string.sFeedback);
    subject         = String.format(subject, 
                      getActivity().getResources().getString(R.string.productName));
    String content  = getActivity().getResources().getString(R.string.whatFeedbackWouldYouLikeToProvide) + "\n\n" + 
                      mMessage.getText().toString();
    File toSend     = new File(outfile);

    if(toSend.exists()) {
        Log.e("Feedback", "File path: " + toSend.getAbsolutePath());

        Intent emailIntent = new Intent(android.content.Intent.ACTION_SENDTO);
        emailIntent.setData(Uri.parse("mailto:" +email));
        emailIntent.putExtra(android.content.Intent.EXTRA_STREAM,   Uri.fromFile(toSend));
        emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,  subject);               
        emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,     content);  

    /*  Intent emailIntent = new Intent(Intent.ACTION_SEND);
        emailIntent.setType("message/rfc822");
        emailIntent.putExtra(Intent.EXTRA_EMAIL  , new String[]{email});
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
        emailIntent.putExtra(Intent.EXTRA_TEXT   , content);
        emailIntent.putExtra(Intent.EXTRA_STREAM , Uri.fromFile(toSend)); */

        try {
            startActivity(emailIntent);
        } catch (ActivityNotFoundException anfe) {
            Toast.makeText(getActivity(), getResources().getString(R.string.pleaseInstallAnEmailClientInOrderToSendUsFeedback), 8000).show();
        }
    }

You can see that the filepaths don't seem to be the problem, I've added in some logging which reports:

Samsung Gives:

04-11 11:40:09.953: E/Feedback(6286): File path: /storage/sdcard0/logs.zip

Nexus Gives:

04-11 11:38:59.249: E/Feedback(12702): File path: /storage/emulated/0/logs.zip

(Both based on getExternalStorageDirectory() to ensure cross application access).

Does anybody know why the difference?

lucascaro
  • 16,550
  • 4
  • 37
  • 47
Graeme
  • 25,714
  • 24
  • 124
  • 186
  • should be `Uri.parse("mailto:" + email)` but you're also missing a bracket on that line so it might be question typo. – Colin Pickard Apr 11 '13 at 10:23
  • yes, sorry, typo in question. – Graeme Apr 11 '13 at 10:23
  • something must be wrong in the filePath as the rest of the code is correct. Just cross check the URI refers to valid location or not. Secondly the file that need to be attached must not be in application directory or cache directory. – Shankar Agarwal Apr 11 '13 at 10:25
  • See edited question - as you can see I've added a check for the file existing (as well as logging the absolute path to ensure it's in a viable location) – Graeme Apr 11 '13 at 10:52

6 Answers6

18

The only solution I came up with is the following one. It is a mix of some others I have found while searching for a complete answer. The following will show only email apps and allow including attachments. The most important part was found here: https://stackoverflow.com/a/8550043/4927659

ArrayList<Uri> uris = new ArrayList<>();
uris.add(Uri.parse("file://" + filepath)); 
//filepath is something like that: /mnt/sdcard/DCIM/DSC0001.JPG
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
                "mailto", "example@gmail.com", null));
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Mail subject");
        List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(emailIntent, 0);
        List<LabeledIntent> intents = new ArrayList<>();
        for (ResolveInfo info : resolveInfos) {
            Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
            intent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
            intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"example@gmail.com"});
            intent.putExtra(Intent.EXTRA_SUBJECT, "Mail subject");
            intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); //ArrayList<Uri> of attachment Uri's
            intents.add(new LabeledIntent(intent, info.activityInfo.packageName, info.loadLabel(getPackageManager()), info.icon));
        }
        Intent chooser = Intent.createChooser(intents.remove(intents.size() - 1), "Send email with attachments...");
        chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new LabeledIntent[intents.size()]));
        startActivity(chooser);
Community
  • 1
  • 1
Anton Tarasov
  • 536
  • 5
  • 12
  • Hi, I came up with similar solution. So ugly, that attachment can't be sent via SENDTO. – Goltsev Eugene Oct 03 '17 at 09:06
  • Very nice solution ! I would complete this answer adding a `if` block after the `for` loop to avoid an error when no mail client was found: `if (intents.size() > 0)` – gbaccetta Feb 22 '18 at 13:25
5

Try to resolve activity via ACTION_SENDTO, but actually sending via ACTION_SEND.

Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
                    "mailto", "", null));
            intent.putExtra(Intent.EXTRA_SUBJECT, title);
            intent.putExtra(Intent.EXTRA_TEXT, text);
            intent.putExtra(Intent.EXTRA_STREAM, getSnapshotUri(snapshot, context, event));

            List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivities(intent, 0);
            if (resolveInfos.size() == 0) {
                new AlertDialog.Builder(context)
                        .setMessage(R.string.no_mail_app)
                        .setPositiveButton(R.string.ok, null)
                        .show();
            } else {
                String packageName = resolveInfos.get(0).activityInfo.packageName;
                String name = resolveInfos.get(0).activityInfo.name;

                intent.setAction(Intent.ACTION_SEND);
                intent.setComponent(new ComponentName(packageName, name));

                context.startActivity(intent);
            }
vbevans94
  • 1,500
  • 1
  • 16
  • 15
  • Would still like the user to have the choice of which email client, but this is certainly a very good start to this problem and very clever - if I ever move back to this I'll be trying to improve on this. Thanks! – Graeme Sep 16 '15 at 10:04
  • I used the following to resolve the activity: intent.resolveActivity(getPackageManager()); – Rich Ehmer Aug 29 '16 at 21:57
  • The problem with this answer: it arbitrarily uses the first activity returned from queryIntentActivities – Rich Ehmer Dec 05 '16 at 23:11
  • You can iterate over resolveInfos and find a proper activity. – vbevans94 Dec 15 '16 at 13:42
2

I encountered the similiar problem and was scratching my head all day, until I found a potential answer on a Chinese blog: http://flysnow.iteye.com/blog/1128354

Nearly to the end of the article, it talks about the intent filters that Android's built-in email client has:

<activity
    android:name=".activity.MessageCompose"
    android:label="@string/app_name"
    android:enabled="false">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="mailto" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
    </intent-filter>
    <intent-filter android:label="@string/app_name">
        <action android:name="android.intent.action.SEND" />
        <data android:mimeType="text/plain" />
        <data android:mimeType="image/*" />
        <data android:mimeType="video/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

You can see it has two intent filters to handle SEND and SENDTO intents differently, and only SEND is specified with mimeType. The code snippet was way back to Android 1.6 but it hasn't been changed much. You can find it in the recent version:

https://android.googlesource.com/platform/packages/apps/Email/+/f44b729bff619d0a9f0b1492726351e41c1e5d5d/AndroidManifest.xml

I'm not sure why they don't specify mimeType in SENDTO intent, but that's the way it is, I think most email client probably doing the same way (except for Gmail, it can successfully attach file when using SENDTO intent). Could that also be your case? Therefore, to be safe you should only send attachment using the SEND intent.

hac.jack
  • 586
  • 5
  • 13
0

you could try putting the subject and body in the Uri. This question seems to imply that might resolve the problem.

Community
  • 1
  • 1
Colin Pickard
  • 45,724
  • 13
  • 98
  • 148
  • Nope - make no difference (Code looks uglier though) - File still doesn't get included :) Thanks for trying! – Graeme Apr 11 '13 at 13:08
  • what about `emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, Uri.parse("file://"+ toSend));` as suggested by http://stackoverflow.com/a/1279574 – Colin Pickard Apr 11 '13 at 13:45
0

The thing is Intent.ACTION_SEND is handled by different email clients correctly even if recepient is specified (that is incorrect, but..). So one must use this action if wants to support other clients than Gmail. That is just how it works.

And to add recepient just add exactly the same line you'd add if were using Intent.ACTION_SENDTO:

Intent emailIntent = new Intent(Intent.ACTION_SEND);
String mailto = "abc@def.com";
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailTo});
...

In my case file attachment was not added in clients other than Gmail with Intent.ACTION_SENDTO and switching to Intent.ACTION_SEND solved it.

Nikita Barishok
  • 1,312
  • 1
  • 12
  • 15
0

After a lot of trying and combining, this is the version I use to send multiple attachments only in an email apps:

public static void sendMailWithAttachedFiles(Context activityContext, ArrayList<String> filePaths, String MAIL, String SUBJECT, String TEXT) {
    ArrayList<Uri> uris = new ArrayList<>();
    for (String filePath : filePaths) {
        File file = new File(filePath);

        Uri csvUri = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            csvUri = FileProvider.getUriForFile(activityContext, activityContext.getPackageName() + ".provider", file);
        } else {
            //doesn't work in API 24+
            csvUri  =   Uri.fromFile(file);
        }

        uris.add(csvUri);
    }

    Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", MAIL, null));
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, SUBJECT);
    emailIntent.putExtra(Intent.EXTRA_TEXT, TEXT);


    List<ResolveInfo> resolveInfos = activityContext.getPackageManager().queryIntentActivities(emailIntent, 0);
    List<LabeledIntent> intents = new ArrayList<>();
    for (ResolveInfo info : resolveInfos) {
        Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        intent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
        intent.putExtra(Intent.EXTRA_EMAIL, new String[]{MAIL});
        intent.putExtra(Intent.EXTRA_SUBJECT, SUBJECT);
        intent.putExtra(Intent.EXTRA_TEXT, TEXT);
        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
        intents.add(new LabeledIntent(intent, info.activityInfo.packageName, info.loadLabel(activityContext.getPackageManager()), info.icon));
    }
    Intent chooser = Intent.createChooser(intents.remove(intents.size() - 1), "Send email using");
    chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new LabeledIntent[intents.size()]));
    activityContext.startActivity(chooser);
}

in Manifest:

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths" />
    </provider>

Create xml folder in res folder and add this file provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>
Guy4444
  • 1,411
  • 13
  • 15