Android Code Snippet Correct Intent to compose an email on Android

I had to add an "Send feedback" an my app, there are lots of examples in the forum to do this, but, all of them are wrong.

The main problem is that they dont create a TRUE email intent, so, many other apps (NO mail clients) are shown on the IntentChooser, like Google drive, Telegram, etc.


Its is bad if you put a "Send email" button aon your app and the user is taken to his Google Drive.


Wrong Code:
B4X:
    Dim Message As Email
    Dim intent1 As Intent
    Message.To.Add("[email protected]")
    Message.Subject = "Feedback" 
    intent1 = Message.GetIntent
    StartActivity(intent1)

Incorrect.jpg





Correct Code:
B4X:
    Dim intent1 As Intent
    intent1.Initialize("android.intent.action.SENDTO", "mailto:[email protected]")
    intent1.putExtra("android.intent.extra.SUBJECT", "This is the subject")
    intent1.putExtra("android.intent.extra.TEXT", "This is the message body")
    intent1.WrapAsIntentChooser("Send feedback")
    StartActivity(intent1)

Correct.jpg


Using this metod, will show only mail apps in the IntentChooser.



Please like it this was helpfull to you
 
Last edited:

Douglas Farias

Expert
Licensed User
Longtime User
only for question
if the android dont have mail apps? this code crash?
 

ivan.tellez

Active Member
Licensed User
Longtime User
only for question
if the android dont have mail apps? this code crash?

NO.


If you use the intent chooser (intent1.WrapAsIntentChooser("Send feedback")) It will display the same dialog as in the picture of the "Correct Code", but with a text warning that there are not apps to perform the action. The downside, is that your app cant tell if there are mail apps.

But if you delete the WrapAsIntentChooser, the app will crash if there are no email apps. You can use this:


B4X:
Try
    Dim intent1 As Intent
    intent1.Initialize("android.intent.action.SENDTO", "mailto:[email protected]")
    intent1.putExtra("android.intent.extra.SUBJECT", "This is the subject")
    intent1.putExtra("android.intent.extra.TEXT", "This is the message body")
    StartActivity(intent1)
Catch
    ToastMessageShow("No mail apps", True)
End Try
 

Kevin

Well-Known Member
Licensed User
Longtime User
Interesting! Can we add an attachment using this method?
 

Kevin

Well-Known Member
Licensed User
Longtime User
I've been trying to attach a text file but I just can't seem to do it. The file is stored in ExternalStorage.

Using the original ('wrong') method, the attachment works fine but not with the new method. Anyone have any ideas?

B4X:
SendAtt = File.DirRootExternal & "/DirecTV_Remote/DirecTVRemote_ini.txt"

Dim intent1 As Intent
intent1.Initialize("android.intent.action.SENDTO", "mailto:[email protected]")
intent1.putExtra("android.intent.extra.SUBJECT", "This is the subject")
intent1.putExtra("android.intent.extra.TEXT", "This is the message body")
If SendAtt <> "" Then
    intent1.putExtra("android.intent.extra.STREAM", SendAtt)
End If
intent1.WrapAsIntentChooser("Send feedback")
StartActivity(intent1)

This worked using the old method but of course shows apps other than just email:

B4X:
Dim EmailIntent As Email
EmailIntent.To.Add (SendTo)
EmailIntent.Body = SendBody
EmailIntent.Subject = SendSub
If SendAtt <> "" Then
    EmailIntent.Attachments.Add(SendAtt)
End If

StartActivity(EmailIntent.GetIntent)
 
Last edited:

Kevin

Well-Known Member
Licensed User
Longtime User
I tried it with the leading "file://" yesterday as well and either way it doesn't seem to attach a file. I thought perhaps it was some kind of folder/file access problem but I'm not so sure. For one, it is in a folder in "external root", but also it works with the old method anyway.

It must be something simple, but I can't seem to figure it out.
 

ivan.tellez

Active Member
Licensed User
Longtime User
Actually, It cant be done this way:

  • ACTION_SENDTO (for NO attachment)
  • ACTION_SEND (for one attachment)
  • ACTION_SEND_MULTIPLE (for multiple attachments)

But, because the way B4A implements the intent Object, cant set the correct data in the Intent to select mail only apps with attachments.
 

Kevin

Well-Known Member
Licensed User
Longtime User
I wonder if it is possible to do with reflection or JO?
 

Kevin

Well-Known Member
Licensed User
Longtime User
Hmmm. I can't try this right now but I wonder if this would allow attachments but also show only email apps in the list:


B4X:
Dim EmailIntent As Email
EmailIntent.To.Add (SendTo)
EmailIntent.Body = SendBody
EmailIntent.Subject = SendSub
If SendAtt <> "" Then
  EmailIntent.Attachments.Add(SendAtt)
End If

Dim intent1 As Intent = EmailIntent.GetIntent
intent1.WrapAsIntentChooser("Send feedback")

StartActivity(intent1)
 

Kevin

Well-Known Member
Licensed User
Longtime User
This will show all apps that registered to the ACTION_SEND action. If there is more than one attachment then ACTION_SEND_MULTIPLE will be used.

In that case this will probably have the same result then. So perhaps it is just an issue in how Android (or its apps) utilize the intents?

Is there a way to change the intent via reflection (or otherwise) to force it to only work with apps registered as email apps?
 

Kevin

Well-Known Member
Licensed User
Longtime User
It would appear that there is no built-in method of doing this regarding Android. However, the code below sounds promising but it looks like we'd have to put it in our own custom "chooser" list. I think it would be a decent solution for those of us who would like to be able to send attachments AND also only show actual email apps to the user.

Lifted from: http://stackoverflow.com/questions/6506637/only-email-apps-to-resolve-an-intent (3rd answer down)

Here is how I send email with attachments (proved to work with attachments of various MIME types, even in the same email) and only allow email apps (it also has a solution for cases that no app support "mailto"). At first, we try and get activities that support mailto: format. If none are found then we get all activities that supports the message/rfc822 MIME type. We select the default app (if there is a default) or allow the user to select from the available apps. If no app supports mailto: and message/rfc822, then we use the default chooser.


B4X:
public static void sendEmail(final Context p_context, final String p_subject, final String p_body, final ArrayList<String> p_attachments)
{
    try
    {
        PackageManager pm = p_context.getPackageManager();
        ResolveInfo selectedEmailActivity = null;

        Intent emailDummyIntent = new Intent(Intent.ACTION_SENDTO);
        emailDummyIntent.setData(Uri.parse("mailto:[email protected]"));

        List<ResolveInfo> emailActivities = pm.queryIntentActivities(emailDummyIntent, 0);

        if (null == emailActivities || emailActivities.size() == 0)
        {
            Intent emailDummyIntentRFC822 = new Intent(Intent.ACTION_SEND_MULTIPLE);
            emailDummyIntentRFC822.setType("message/rfc822");

            emailActivities = pm.queryIntentActivities(emailDummyIntentRFC822, 0);
        }

        if (null != emailActivities)
        {
            if (emailActivities.size() == 1)
            {
                selectedEmailActivity = emailActivities.get(0);
            }
            else
            {
                for (ResolveInfo currAvailableEmailActivity : emailActivities)
                {
                    if (true == currAvailableEmailActivity.isDefault)
                    {
                        selectedEmailActivity = currAvailableEmailActivity;
                    }
                }
            }

            if (null != selectedEmailActivity)
            {
                // Send email using the only/default email activity
                sendEmailUsingSelectedEmailApp(p_context, p_subject, p_body, p_attachments, selectedEmailActivity);
            }
            else
            {
                final List<ResolveInfo> emailActivitiesForDialog = emailActivities;

                String[] availableEmailAppsName = new String[emailActivitiesForDialog.size()];
                for (int i = 0; i < emailActivitiesForDialog.size(); i++)
                {
                    availableEmailAppsName[i] = emailActivitiesForDialog.get(i).activityInfo.applicationInfo.loadLabel(pm).toString();
                }

                AlertDialog.Builder builder = new AlertDialog.Builder(p_context);
                builder.setTitle(R.string.select_mail_application_title);
                builder.setItems(availableEmailAppsName, new DialogInterface.OnClickListener()
                {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        sendEmailUsingSelectedEmailApp(p_context, p_subject, p_body, p_attachments, emailActivitiesForDialog.get(which));
                    }
                });

                builder.create().show();
            }
        }
        else
        {
            sendEmailUsingSelectedEmailApp(p_context, p_subject, p_body, p_attachments, null);
        }
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Can't send email", ex);
    }
}

protected static void sendEmailUsingSelectedEmailApp(Context p_context, String p_subject, String p_body, ArrayList<String> p_attachments, ResolveInfo p_selectedEmailApp)
{
    try
    {
        Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);

        String aEmailList[] = { "[email protected]"};

        emailIntent.putExtra(Intent.EXTRA_EMAIL, aEmailList);
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, null != p_subject ? p_subject : "");
        emailIntent.putExtra(Intent.EXTRA_TEXT, null != p_body ? p_body : "");

        if (null != p_attachments && p_attachments.size() > 0)
        {
            ArrayList<Uri> attachmentsUris = new ArrayList<Uri>();

            // Convert from paths to Android friendly Parcelable Uri's
            for (String currAttachemntPath : p_attachments)
            {
                File fileIn = new File(currAttachemntPath);
                Uri currAttachemntUri = Uri.fromFile(fileIn);
                attachmentsUris.add(currAttachemntUri);
            }
            emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachmentsUris);
        }

        if (null != p_selectedEmailApp)
        {
            Log.d(TAG, "Sending email using " + p_selectedEmailApp);
            emailIntent.setComponent(new ComponentName(p_selectedEmailApp.activityInfo.packageName, p_selectedEmailApp.activityInfo.name));

            p_context.startActivity(emailIntent);
        }
        else
        {
            Intent emailAppChooser = Intent.createChooser(emailIntent, "Select Email app");

            p_context.startActivity(emailAppChooser);
        }
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Error sending email", ex);
    }
}
 

Kevin

Well-Known Member
Licensed User
Longtime User
Success! I was able to get working B4A code from the Java example above. It isn't exactly the same but I believe it will provide the necessary functions. Only thing left to do would be to create a nice looking dialog for it complete with app icons. For now it just shows the results in an InputList dialog but I added the icon part in case anyone would want to work with this.

If there is no attachment then it will use the method Ivan shared in this thread, complete with a native app chooser. If there is an attachment it will create a custom list shown in the InputList dialog.

Tested with stock email app on Samsung Galaxy S5, GMail and AquaMail.

B4X:
Sub SendEmail (SendTo As String, SendBody As String, SendSub As String, SendAtt As String)
    Dim FinalEmailIntent As Intent, sPackageName As String

    If SendAtt = "" Then ' Use new method - will not work with attachments
         FinalEmailIntent.Initialize("android.intent.action.SENDTO", "mailto:" & SendTo)
        FinalEmailIntent.putExtra("android.intent.extra.SUBJECT", SendSub)
         FinalEmailIntent.putExtra("android.intent.extra.TEXT", SendBody)
        FinalEmailIntent.WrapAsIntentChooser("Send E-mail")
            Else ' Make our own list of email apps
                sPackageName = GetEmailPackage
                If sPackageName = "/cancel/" Then Return
                If sPackageName = "" Then
                    Msgbox ("Unable to send email.", "")
                    Return
                End If
            
                Dim MyEmail As Email
                MyEmail.To.Add (SendTo)
              MyEmail.Body = SendBody
                 MyEmail.Subject = SendSub
              MyEmail.Attachments.Add(SendAtt)
                FinalEmailIntent = MyEmail.GetIntent
                FinalEmailIntent.SetComponent (sPackageName)
    End If

    StartActivity (FinalEmailIntent)

End Sub

Sub GetEmailPackage As String
    Dim PM As PackageManager, DummyIntent As Intent, DummyIntent2 As Intent
    Dim EmailActivities As List, EmailAppNames As List, iPackage As Int, x As Int, tempString As String
    EmailActivities.Initialize: EmailAppNames.Initialize

    DummyIntent.Initialize("android.intent.action.SENDTO", "mailto:[email protected]")
    EmailActivities = PM.QueryIntentActivities (DummyIntent)

    If EmailActivities.Size = 0 Then
         DummyIntent2.Initialize("android.intent.action.SENDMULTIPLE", "")
        DummyIntent2.SetType ("message/rfc822")
        EmailActivities = PM.QueryIntentActivities (DummyIntent2)
    End If

    If EmailActivities.Size = 0 Then Return ""

    ' Get app labels & icons
    For x = 0 To EmailActivities.Size - 1
        Dim imgIcon As BitmapDrawable
        Dim sPackageName As String = EmailActivities.Get(x)
        sPackageName = sPackageName.SubString2 (0, sPackageName.IndexOf ("/"))
        tempString = PM.GetApplicationLabel(sPackageName)
        imgIcon = PM.GetApplicationIcon(sPackageName) 'App icon if desired
        EmailAppNames.Add (tempString)
    Next

    iPackage = InputList (EmailAppNames,"Send email using",-1)
    If iPackage = DialogResponse.CANCEL Then Return "/cancel/"

    Return EmailActivities.Get (iPackage)

End Sub
 
Last edited:

luke2012

Well-Known Member
Licensed User
Longtime User
Success! I was able to get working B4A code from the Java example above. It isn't exactly the same but I believe it will provide the necessary functions. Only thing left to do would be to create a nice looking dialog for it complete with app icons. For now it just shows the results in an InputList dialog but I added the icon part in case anyone would want to work with this.

If there is no attachment then it will use the method Ivan shared in this thread, complete with a native app chooser. If there is an attachment it will create a custom list shown in the InputList dialog.

Tested with stock email app on Samsung Galaxy S5, GMail and AquaMail.

B4X:
Sub SendEmail (SendTo As String, SendBody As String, SendSub As String, SendAtt As String)
    Dim FinalEmailIntent As Intent, sPackageName As String

    If SendAtt = "" Then ' Use new method - will not work with attachments
         FinalEmailIntent.Initialize("android.intent.action.SENDTO", "mailto:" & SendTo)
        FinalEmailIntent.putExtra("android.intent.extra.SUBJECT", SendSub)
         FinalEmailIntent.putExtra("android.intent.extra.TEXT", SendBody)
        FinalEmailIntent.WrapAsIntentChooser("Send E-mail")
            Else ' Make our own list of email apps
                sPackageName = GetEmailPackage
                If sPackageName = "/cancel/" Then Return
                If sPackageName = "" Then
                    Msgbox ("Unable to send email.", "")
                    Return
                End If
           
                Dim MyEmail As Email
                MyEmail.To.Add (SendTo)
              MyEmail.Body = SendBody
                 MyEmail.Subject = SendSub
              MyEmail.Attachments.Add(SendAtt)
                FinalEmailIntent = MyEmail.GetIntent
                FinalEmailIntent.SetComponent (sPackageName)
    End If

    StartActivity (FinalEmailIntent)

End Sub

Sub GetEmailPackage As String
    Dim PM As PackageManager, DummyIntent As Intent, DummyIntent2 As Intent
    Dim EmailActivities As List, EmailAppNames As List, iPackage As Int, x As Int, tempString As String
    EmailActivities.Initialize: EmailAppNames.Initialize

    DummyIntent.Initialize("android.intent.action.SENDTO", "mailto:[email protected]")
    EmailActivities = PM.QueryIntentActivities (DummyIntent)

    If EmailActivities.Size = 0 Then
         DummyIntent2.Initialize("android.intent.action.SENDMULTIPLE", "")
        DummyIntent2.SetType ("message/rfc822")
        EmailActivities = PM.QueryIntentActivities (DummyIntent2)
    End If

    If EmailActivities.Size = 0 Then Return ""

    ' Get app labels & icons
    For x = 0 To EmailActivities.Size - 1
        Dim imgIcon As BitmapDrawable
        Dim sPackageName As String = EmailActivities.Get(x)
        sPackageName = sPackageName.SubString2 (0, sPackageName.IndexOf ("/"))
        tempString = PM.GetApplicationLabel(sPackageName)
        imgIcon = PM.GetApplicationIcon(sPackageName) 'App icon if desired
        EmailAppNames.Add (tempString)
    Next

    iPackage = InputList (EmailAppNames,"Send email using",-1)
    If iPackage = DialogResponse.CANCEL Then Return "/cancel/"

    Return EmailActivities.Get (iPackage)

End Sub

Very interesting @Kevin ! So this could replace the code that use SMTP send method (protcol, user and password as parameters) ?
 

Kevin

Well-Known Member
Licensed User
Longtime User
If I understand you correctly then I would say that this would not replace sending email directly using SMTP. In that case you are directly submitting the email to the server, where as with this all you are doing is allowing the user to send an email using their favorite email app and that app is submitting the email to the server via SMTP.
 

luke2012

Well-Known Member
Licensed User
Longtime User
If I understand you correctly then I would say that this would not replace sending email directly using SMTP. In that case you are directly submitting the email to the server, where as with this all you are doing is allowing the user to send an email using their favorite email app and that app is submitting the email to the server via SMTP.

If I understand you and the code correctly, it imply an user interaction (UI and not background process) each time it runs ?

My target:
I wish to replace the code within my app that ask info like user name and password to the user in order to parametrize the sender sub and introduce a code that use the current registered gmail account (without ask user credentials) to send emails in background mode when user tap on a specific button.
 

Kevin

Well-Known Member
Licensed User
Longtime User
I suppose the answer to what you are looking for depends on your purpose for sending these emails. But regarding the samples in this thread, yes it requires user interaction. Long story short, all it is doing and all it can do is present the user with a list of email apps on their phone with which to then send an email. I use it to allow users to send me an email that automatically includes the app name in the subject and the body is pre-populated with technical details about how the app is configured in order to help me understand their problem. It can also attach log and config files if necessary.

I don't think that there is any way to send an email through the user's gmail account without their interaction at all. That would be a huge security risk.
 
Top