1

Based on this post, I am opening an email client to send multiple attachments with the following code:

Intent emailSelectorIntent = new Intent(Intent.ACTION_SENDTO);
emailSelectorIntent.setData(Uri.parse("mailto:"));

Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.putExtra(Intent.EXTRA_EMAIL, addresses);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);

ArrayList<Uri> uriList = new ArrayList<>();
uriList.add(FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", fileA));
uriList.add(FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", fileB));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, uriList);

intent.setSelector(emailSelectorIntent);

if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

Note that, as taught in the linked post above, I need two Intents to make it work for sending attachments (and limiting the selector to only email clients)... emailSelectorIntent and intent, with the former being passed to the latter via intent.setSelector(emailSelectorIntent).

In order to make the selector open up, I also need the following in my Manifest:

<intent-filter>
    <action android:name="android.intent.action.SENDTO" />
    <data android:scheme="mailto" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

But when the selector opens, not only do I get email clients showing (e.g. gmail) I am also getting my own app in the list... which is not capable of sending emails.

This post suggests not putting android.intent.action.SENDTO in the Manifest, but if I remove that then the selector no longer opens at all. If put it back, the selector opens but with my own app as an option for an email client (which it is not).

So how do I avoid my own app showing up in the selector list in this context?

Note that my targetSdk is 32 in case that's relevant, which it may be (see comment from @CommonsWare here).

drmrbrewer
  • 11,491
  • 21
  • 85
  • 181
  • You can exclude your app from selector list by following this (https://stackoverflow.com/questions/4064848/how-to-exclude-your-own-app-from-the-share-menu#answer-17781876) – Umesh P May 18 '22 at 12:54
  • "This post suggests not putting android.intent.action.SENDTO in the Manifest" -- "suggests" is understating it. By putting that manifest entry there, you are advertising **to every app on the device** that you support that action. So, your app will show up as an option in many other apps, not just as an option in your own app. That does not appear to be what you want. "So how do I avoid my own app showing up in the selector list in this context?" -- get rid of that ``, as your problem is not limited to "this context". – CommonsWare May 18 '22 at 13:07
  • "if I remove that then the selector no longer opens at all" -- what specifically happens? What version(s) of Android are you testing on? – CommonsWare May 18 '22 at 13:08
  • @CommonsWare interestingly, without `android.intent.action.SENDTO` in the Manifest, `startActivity()` is never called because the test `intent.resolveActivity(getPackageManager()) != null` fails. And more interestingly, removing that test entirely, and just calling `startActivity()` regardless, does open up the selector with only email clients showing (and not my app). But surely that test is there for a reason, i.e. to ensure that there is an email client available? I'm testing this on `32` only... perhaps the solution does relate to `` as you suggested? – drmrbrewer May 18 '22 at 13:35
  • @CommonsWare yes I confirm that removing the `` and instead putting an equivalent entry in the top level of the Manifest as `...` makes it work... though not sure how this will work on older versions of Android? If you add an answer I can mark it as correct. – drmrbrewer May 18 '22 at 13:47

3 Answers3

2

I'm somewhat mystified that having that <intent-filter> changes anything. Regardless, you really don't want that there, as it can cause other apps to route ACTION_SENDTO requests to your app, which you don't want.

without android.intent.action.SENDTO in the Manifest, startActivity() is never called because the test intent.resolveActivity(getPackageManager()) != null fails

That is due to package visibility rules. Your options are to either add <queries> to say that you need to see who all supports your Intent, or to replace the resolveActivity() call with a try/catch:

try {
    startActivity(intent);
} catch (Exception e) {
    // whatever you want to do to gracefully degrade in case there is no suitable activity to start
}

not sure how this will work on older versions of Android?

<queries> will be ignored on older versions of Android, but package visibility isn't a problem on older versions of Android. Personally, I prefer try/catch, as it handles more scenarios.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    Thanks, very helpful. Just for the record, I added another answer to expand on the `` alternative, but have marked yours as the accepted answer. – drmrbrewer May 18 '22 at 14:59
  • I'm having reports from a couple of users that an email chooser dialog isn't opening at all for them, which suggests that their system doesn't think that there are any apps able to handle email. In one case the user is using FairEmail, so my thought was that maybe FairEmail isn't correctly advertising its capabilities. But another user is using only Gmail, which I know for sure works for me. The latter user did have Outlook installed previously, but now uninstalled. Do you have any idea what might be going on here? (And it's the same with the `queries` alternative below.) – drmrbrewer Mar 05 '23 at 08:32
  • @drmrbrewer: It is possible that `` might need to cover both your base `Intent` and the selector `Intent`. Also note that not all Gmail installations are identical, and I have run into similar issues where Gmail on device 1 behaves differently than does Gmail on device 2. – CommonsWare Mar 06 '23 at 12:02
  • I did consider that (i.e. for `ACTION_SEND_MULTIPLE` in my case) but wasn't sure whether `data` on the base `Intent` should be `` given that I'm only calling `setData(Uri.parse("mailto:"))` on the selector `Intent`... or whether it should be something else. Or maybe I should call `setData(Uri.parse("mailto:"))` on the base `Intent` too? – drmrbrewer Mar 06 '23 at 12:20
  • UPDATE: adding an equivalent `` entry for `ACTION_SEND_MULTIPLE` didn't help with the FairEmail user at least... still no dialog is opened :-( – drmrbrewer Mar 06 '23 at 16:10
  • I wonder if there have been any changes in Android 13. All affected users are on Android 13. When I test this on an Android 13 emulator with Gmail and FairEmail installed, and the "defaults" cleared from Gmail so it shouldn't assume it is the default email app, I'm finding that Gmail opens directly, despite not being the default, and despite it not being the only email client... it should open the email client chooser? – drmrbrewer Mar 06 '23 at 22:09
  • @drmrbrewer: Looking at [the FairEmail manifest](https://github.com/M66B/FairEmail/blob/master/app/src/main/AndroidManifest.xml#L325-L331), I notice that you don't seem to be specifying a MIME type, at least in the code in your question. Have you tried adding a MIME type (`*/*` if you can't be more specific) to your `ACTION_SEND_MULTIPLE` `Intent` to see if that has an impact? – CommonsWare Mar 06 '23 at 22:45
  • That's a good suggestion, I'll try it. Do you mean call `intent.setType("*/*")` in my example code? And in the manifest, it seems like you can have `` or `` but not both. – drmrbrewer Mar 06 '23 at 23:48
  • Hmm, calling `intent.setType("*/*")` makes it break even on my own device where it normally works. So do you mean just amending the `` entry to refer to `` associated with ``? – drmrbrewer Mar 06 '23 at 23:59
  • @drmrbrewer: No, I meant `intent.setType("*/*")`, though now that you mention it, you probably also need to add it to the relevant `` entry -- sorry for not thinking of that earlier! – CommonsWare Mar 07 '23 at 00:34
  • When I add `intent.setType("*/*")` and a corresponing `SEND_MULTIPLE` manifest `` entry with ``, no email client is found on my device (which works fine without these changes). But leaving those in and *not* calling `intent.setSelector(...)` *does* open up an app chooser dialog on my device, this time not limited to just email clients, and selecting Gmail from that does then work OK. – drmrbrewer Mar 07 '23 at 14:16
  • It seems that there is no reliable way to get this to work on all devices?! Perhaps the only way is to try first with `intent.setSelector(...)` and then, if no email client is found, try again without it? – drmrbrewer Mar 07 '23 at 14:16
  • I've added a [further answer](https://stackoverflow.com/a/75663126/4070848) based on that approach (try first with, then without)... but rather than not using `intent.setSelector(...)`, the new answer just passes that `intent` through `Intent.createChooser(...)` and tries again. So far, it seems to work with all devices, but no doubt it'll be one step forward now and I'll later discover that it is two steps back. – drmrbrewer Mar 07 '23 at 14:19
2

The answer from @CommonsWare is (as ever) very pragmatic and informative. I think I prefer the try/catch solution proposed there too, and will adopt it. But the alternative of leaving the resolveActivity() check and adding a <queries> item in the top level of the Manifest also works, like so:

<queries>
    <intent>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="mailto" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent>
</queries>
drmrbrewer
  • 11,491
  • 21
  • 85
  • 181
0

This is an extension to the answer from @CommonsWare as a possible workaround to an issue with (some?) Android 13 devices with (some?) email clients. I don't really understand why there is an issue, or exactly when it occurs, but the following workaround seems to help.

Basically, for these problem setups, the first attempt to startActivity(intent) fails, because apparently no email client is available (even though it is). But rather than giving up at that point, we instead create a "chooser" Intent based on the base Intent, and pass that to startActivity(chooser).

try {
    startActivity(intent);
} catch (Exception e1) {
    try {
        // rather than give up straight away, try again but passing the intent through createChooser()...
        Intent chooser = Intent.createChooser(intent, "Send email using...");
        startActivity(chooser);
    } catch (Exception e2) {
        // give up... e.g. show a "cannot find email client" message...
    }
}

So far, this seems to work more reliably on all devices. This also seems to be the approach taken here.

As a side note, I'm still confused about package visibility rules, which seem to require adding <queries> entries in the manifest for this package filtering to work at all on Android 11+ (see e.g. this answer)... but even if the manifest doesn't have any of these, it doesn't seem to make any difference to how this email intent handoff works.

drmrbrewer
  • 11,491
  • 21
  • 85
  • 181
  • No matter what I do I can't get this to work on a real device running MIUI 14 (Android 13). It falls through to the chooser intent and does nothing. I have tested on a Pixel emulator running Android 11 and 13 respectively and it works without issue with the first intent. I have tried adding relevant ` .. ` to the manifest but this, unsurprisingly, has no effect. This answer (and similar) works https://stackoverflow.com/a/19011675/4252352 but is not desireable as it shows a plethora of apps that are not speciifically email clients. Any ideas? Seems vendor related. – Mark Apr 17 '23 at 22:36