Android Question Sending an attachment by email

andyp

Member
Licensed User
Hi. I am trying to send a file as an attachment by email from within my app, using an email application already on the phone (ie not just by SMTP directly from within the app). In my case I am using gmail.

gmail gives an error 'permission denied for the attachment'.

Any ideas how to solve this? Thank you!

B4X:
Dim Message As Email
    Message.To.Add("[email protected]")
    Message.Subject = "Subject"
    Message.Body = "Message body."
    Message.Attachments.Initialize
    Message.Attachments.Add(File.Combine(File.DirInternal&"/mydirectory", "myfile"))
    StartActivity(Message.GetIntent)
 

rboeck

Well-Known Member
Licensed User
Longtime User
Try some copy commands to transfer the file to another place, maybe that shows the problem...
 
Upvote 0

andyp

Member
Licensed User
Thanks - The problem appears to be gmail does not have permission to access the DirInternal directory.

I copied the file to DirDefaultExternal, and gmail will then happily send the attachment. I delete the copied file after it is sent .......

Is there a better directory to save the (temporary) file to?
 
Upvote 0

BillMeyer

Well-Known Member
Licensed User
Longtime User
Yes, I do have an idea. I'll share it with you.

Firstly, make sure you have ticked "Runtime Permissions" Library for use

Secondly put this in your manifest (assuming you are using Android 7 already)

B4X:
'********************* Android 7 Stuff ****************************************
AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <external-files-path name="name" path="shared" />
)


'********************* End A7 Stuff *******************************************

The put this in your Starter module

B4X:
Sub Process_Globals
 
    Public rp As RuntimePermissions
    Public shared As String
    Public GlobPath As String

End Sub

Sub Service_Create

    shared = rp.GetSafeDirDefaultExternal("shared")
 
    If File.ExternalWritable Then
        File.MakeDir(File.DirRootExternal, "scripts")  ' you can use basically any sub directory you wish here
        GlobPath = File.DirRootExternal & "/scripts/"
        Log("dir: " & GlobPath)
    Else
        File.MakeDir(File.DirRootExternal, "scripts")
        GlobPath = File.DirRootExternal & "/scripts/"
        Log("dir: " & GlobPath)
    End If


End Sub

Now in your module where you want to send the attachment:

B4X:
Sub Globals
    Dim path1 As String = Starter.GlobPath
    Dim GlobFileName As String = "yourFileName.yourExt" ' Use your filename and extension as required

End Sub


Sub btnSend_Click
    File.Copy(path1, GlobFileName, Starter.shared, GlobFileName) 'Put the file in a shared space ready for attaching
    Dim SendTo As String = "[email protected]"
    Dim SendBody As String = "My Body Text Here"
    Dim SendSub As String = "My Example Subject"
 
    SendEmail (SendTo, SendBody, SendSub,File.Combine(path1, GlobFileName ))

End Sub


Sub SendEmail (SendTo As String, SendBody As String, SendSub As String, SendAtt As String)
    Dim FinalEmailIntent As Intent, sPackageName As String
    LogColor("Send Att: "&SendAtt,Colors.Red)

    If SendAtt = "" Then ' Use new method - will not work with attachments
    File.Copy(path1, GlobFileName, Starter.shared, GlobFileName)
 
        FinalEmailIntent.Initialize("android.intent.action.SENDTO", "mailto:" & SendTo)
        FinalEmailIntent.putExtra("android.intent.extra.SUBJECT", SendSub)
        FinalEmailIntent.putExtra("android.intent.extra.TEXT", SendBody)
        FinalEmailIntent.PutExtra("android.intent.extra.STREAM",  CreateFileProviderUri(Starter.shared, GlobFileName))
        FinalEmailIntent.Flags = 1
        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

Sub CreateFileProviderUri (Dir As String, FileName As String) As Object
    Dim FileProvider As JavaObject
    Dim context As JavaObject
    context.InitializeContext
    FileProvider.InitializeStatic("android.support.v4.content.FileProvider")
    Dim f As JavaObject
    f.InitializeNewInstance("java.io.File", Array(Dir, FileName))
    Return FileProvider.RunMethod("getUriForFile", Array(context, Application.PackageName & ".provider", f))
End Sub

and that Sir, should do it. It will give you a choice as to the email provider you wish to use and put the attachment file in a safe place for sharing. This will enable Android 7 and greater to work as well.

Pay forward now and help others on this forum.

ACKNOWLEDGEMENTS:

All the brothers in this thread: especially @Kevin

@DonManfred for pointing out the obvious to me in a file error configuration
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
@DonManfred for pointing out the obvious to me in a file error configuration
So i can asume my suggestion in the other thread did worked? I asked you in a private message about but i did not get an answer

Great it did work :)
 
Upvote 0

andyp

Member
Licensed User
Thanks Bill - that's a lot of code to digest :) I might stick with copying the file to DirDefaultExternal, unless there is a particular reason not to do this....
 
Upvote 0

BillMeyer

Well-Known Member
Licensed User
Longtime User
Thanks Manfred - you're a star as always - Apologies for the late reply - only getting to my Admin now.
 
Upvote 0

BillMeyer

Well-Known Member
Licensed User
Longtime User
Thanks Bill - that's a lot of code to digest :) I might stick with copying the file to DirDefaultExternal, unless there is a particular reason not to do this....

The only reason is to give your user the choice of eMail carrier they want to use and secondly to avoid any problems with Android 7 and greater permissions. Other than that no reasons.
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
I might stick with copying the file to DirDefaultExternal, unless there is a particular reason not to do this
Starting from Android 7 you need to use a fileprovider to share a file. Or to use it in an email intent. The point is that the app which usess the file (the email client for example) do not have the permission to use a file using the file:// uri....

So, if you are targetting Android 25 or above (targetsdk in manifest) it is mandatory to use a fileprovider for such cases.
 
Upvote 0

andyp

Member
Licensed User
@BillMeyer

I have taken your advice re: using fileprovider into the future.... I am unfortunately a bit lost, and hope someone can help.....

My file is currently here: File.DirInternal&"/mydirectory", "myfile"

Where do I need to copy my file to as it will work with your code? Or where do I put this path and file name? I guess the file name position is obvious (Dim GlobFileName), but what about the path?

Sorry if this should be obvious :)

B4X:
Sub Globals
     Dim path1 As String = Starter.GlobPath
     Dim GlobFileName AsString = "yourFileName.yourExt"  ' Use your filename and extension as required
End Sub

Sub btnSend_Click
    File.Copy(path1, GlobFileName, Starter.shared, GlobFileName) 'Put the file in a shared space ready for attaching
    Dim SendTo As String = "[email protected]"
    Dim SendBody As String = "My Body Text Here"
    Dim SendSub As String = "My Example Subject"
    SendEmail (SendTo, SendBody, SendSub,File.Combine(path1, GlobFileName ))
End Sub
 
Upvote 0

andyp

Member
Licensed User
My file is currently in File.DirInternal

I copied it to DirDefaultExternal as you suggested

But where do I point to this path in @BillMeyer code above? Again, sorry if this should be obvious.... but I am missing something here....
 
Upvote 0

Alessandro71

Well-Known Member
Licensed User
Longtime User
how did you handle deletion of the file you copied in "shared"?
it should be deleted after StartActivity of the email intent, but StartActivity is asynch, so you must make sure email has really been sent before deleting it.
 
Upvote 0

andyp

Member
Licensed User
Hi Alessandro

I used the code as suggested by Bill Meyer above, and did not delete the file - I use the same file name each time, so the file just gets overwritten....

Sorry I can’t be of help...

Andrew
 
Upvote 0

Alessandro71

Well-Known Member
Licensed User
Longtime User
I understand.
Be careful: I once had a problem where one user was getting data that belonged to another, just because I was reusing the same file name every time.
 
Upvote 0

andyp

Member
Licensed User
Hi.

This method of forming an email has been working great. Unfortunately with some android updates/changes, this is no longer working.

Issues:
1. The app would crash copying the file
B4X:
    File.Copy(path1, GlobFileName, Starter.shared, GlobFileName)
I 'fixed' this by manually going to the phone settings, and changing the app storage permissions to on.

- What is the current method to ask for these permissions in code?

2. The app then crashes here:
B4X:
    SendEmail (SendTo, SendBody, SendSub,File.Combine(path1, GlobFileName ))

The log output is below.
Any ideas as to the cause of the crash, and what to look at to fix this?

Thank you!
Andrew

Send Att: /storage/emulated/0/scripts/EmailedBox.f3a
loadthebox_getemailpackage (java line: 723)
java.lang.ClassCastException: android.graphics.drawable.AdaptiveIconDrawable cannot be cast to android.graphics.drawable.BitmapDrawable
at f3azone.pro.reader.loadthebox._getemailpackage(loadthebox.java:723)
at f3azone.pro.reader.loadthebox._sendemail(loadthebox.java:850)
at f3azone.pro.reader.loadthebox$ResumableSub_Button3_Click.resume(loadthebox.java:545)
at f3azone.pro.reader.loadthebox._button3_click(loadthebox.java:491)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:191)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:80)
at android.view.View.performClick(View.java:6897)
at android.widget.TextView.performClick(TextView.java:12693)
at android.view.View$PerformClick.run(View.java:26100)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6942)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
 
Upvote 0
Top