Android Tutorial Using POP3 to communicate with Android devices

Many developers face the challenge of sending data to remote devices. These devices can be at times offline, sleeping or without proper network coverage.
There are several possible solutions. The device can contact a web server which will return the required data.
Another solution is to use Google push notification framework.
Here I want to present a third solution which doesn't require any custom server and is pretty simple.
Using the Net library a device can connect to a mail server and download the mail messages.
This solution can fit very well in many cases and is very simple to manage. Mail servers are very common and are easy to work with.
All you need is an email service that supports POP3. Gmail is one such service.

The POP3 object from the Net library returns the raw messages as strings.
Additional work is required to get the message fields and especially to extract attachments from the messages.
MailParser code module is included in the attached project.
MailParser parses the messages, saves the attachments and returns Messages objects which hold the various fields.

MailParser cannot be used as a real mail client. There are many possible formats and encodings. MailParser can handle simple formats with zero or more attachments.

Parsing a message is done by calling:
B4X:
      Dim m As Message
      m = MailParser.ParseMail(MessageText, File.DirRootExternal)
The first parameter is the raw message and the second is the folder that the attachments will be saved to.

MailParser requires StringUtils library for the base64 decoding.
 

Attachments

  • MailParser.zip
    6.5 KB · Views: 2,659

NeoTechni

Well-Known Member
Licensed User
Longtime User
I'm having a problem with it.

It stops at the first : in the subject, so if the subject is Re: this is a test, the mailparser returns the subject is Re
 

NeoTechni

Well-Known Member
Licensed User
Longtime User
I think this will fix it

B4X:
Sub Right(Text As String, Length As Long) As String
   If Text.Length>0 AND Length>0 Then
      'If Length>Text.Length Then Length=Text.Length 
      Return Text.SubString(Text.Length-Min(Text.Length,Length))
   End If
   Return ""
End Sub

Sub ParseHeaders (Mail As String, Msg As Message)
   Dim line As String
   Log("MAIL: " & Mail)
   line = ReadNextLine(Mail)
   Do While line.Length > 0
      Dim parts() As String,first As String, second As String
      parts = Regex.Split(":", line)
      If parts.Length>2 Then   parts(1)= Right(line, line.Length - (parts(0).Length +1) )
      If parts.Length >= 2 Then
         first = parts(0).ToLowerCase
         Select first
            Case "from":   Msg.FromField = parts(1)
            Case "to":      Msg.ToField = parts(1)
            Case "cc":      Msg.CCField = parts(1)
            Case "bcc":      Msg.BCCField = parts(1)
            Case "subject":   Msg.Subject = parts(1)
            Case "content-type"
               second =  parts(1).ToLowerCase
               Msg.ContentType = parts(1)
               If second.Contains("multipart/") Then
                  multipart = True
                  If FindBoundary(line) = False Then
                     line = ReadNextLine(Mail)
                     FindBoundary(line)
                  End If
               End If
         End Select   
      End If
      line = ReadNextLine(Mail)
   Loop
End Sub
 

boredsilly

Member
Licensed User
Longtime User
Make sure you initiate recent mode in gmail!

Hi,

Just a gotcha that had me pulling out my hair for ages while trying to read my gmail account. Make sure your username is prefixed with recent: e.g
recent:[email protected] if your pop3 options in gmail keep the email on the server as it reports zero messages to download!

:BangHead:
 

luke2012

Well-Known Member
Licensed User
Longtime User
Goodmorning community!
I got the following error during the pop_listcompleted :

LogCat connected to: B4A-Bridge: LGE LG-E730-358103040185811
--------- beginning of /dev/log/system
--------- beginning of /dev/log/main
<!>anywheresoftware.b4a.BA 171<!> actreceivelists_pop_listcompleted (B4A line: 39)
pop.DownloadMessage(Messages.GetKeyAt(i), False)
[BA.java:172:raiseEvent2()]
java.util.concurrent.RejectedExecutionException: pool=20/20, queue=0/0
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1961)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:794)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1315)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:85)
at anywheresoftware.b4a.B4AThreadPool.submit(B4AThreadPool.java:39)
at anywheresoftware.b4a.BA.submitRunnable(BA.java:301)
at anywheresoftware.b4a.net.POPWrapper.DownloadMessage(POPWrapper.java:197)
at luke2012.android.pizzeria.actreceivelists._pop_listcompleted(actreceivelists.java:370)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:145)
at anywheresoftware.b4a.BA$3.run(BA.java:279)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3740)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:880)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:638)
at dalvik.system.NativeStart.main(Native Method)
<!>anywheresoftware.b4a.BA 177<!> java.util.concurrent.RejectedExecutionException: pool=20/20, queue=0/0
<!>anywheresoftware.b4a.keywords.Common 153<!> Installing file.

*** Any idea abot this?
 

luke2012

Well-Known Member
Licensed User
Longtime User
I'm using this code :

If Success Then
'download all messages
'change last parameter to True if you want to delete the messages from the server
For i = 0 To Messages.Size - 1
pop.DownloadMessage(Messages.GetKeyAt(i), False)
Next
Else
Log(LastException.Message)
End If
 

luke2012

Well-Known Member
Licensed User
Longtime User
Sorry but I think that a customer pay for new features not to solve bugs.

Anyway thanks for the information.
 

luke2012

Well-Known Member
Licensed User
Longtime User
I did the amendments to the code and now the procedure work fine until 1 to 153° message.

The 154° message raise the following error :

<!>anywheresoftware.b4a.keywords.Common 151<!> 154
<!>anywheresoftware.b4a.BA 171<!> mailparser_handlepart (java line: 97)
[BA.java:172:raiseEvent2()]
java.io.FileNotFoundException: /mnt/sdcard/Android/data/luke2012.android.pizzeria/files/=?ISO-8859-1?Q?La_mobilit=E0_=E8_uno_dei_principali_obiettivi_di_questi_ann?= =?ISO-8859-1?Q?i=2Edoc?= (Invalid argument)
at org.apache.harmony.luni.platform.OSFileSystem.open(Native Method)
at dalvik.system.BlockGuard$WrappedFileSystem.open(BlockGuard.java:232)
at java.io.FileOutputStream.<init>(FileOutputStream.java:94)
at anywheresoftware.b4a.objects.streams.File.OpenOutput(File.java:348)
at luke2012.android.pizzeria.mailparser._handlepart(mailparser.java:97)
at luke2012.android.pizzeria.mailparser._parsemultipartbody(mailparser.java:256)
at luke2012.android.pizzeria.mailparser._parsemail(mailparser.java:218)
at luke2012.android.pizzeria.actreceivelists._pop_downloadcompleted(actreceivelists.java:279)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:145)
at anywheresoftware.b4a.BA$3.run(BA.java:279)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3740)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:880)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:638)
at dalvik.system.NativeStart.main(Native Method)
<!>anywheresoftware.b4a.BA 177<!> java.io.FileNotFoundException: /mnt/sdcard/Android/data/luke2012.android.pizzeria/files/=?ISO-8859-1?Q?La_mobilit=E0_=E8_uno_dei_principali_obiettivi_di_questi_ann?= =?ISO-8859-1?Q?i=2Edoc?= (Invalid argument)
 

luke2012

Well-Known Member
Licensed User
Longtime User
I have analyze the mail that raise error and I see that the attachment name have more than one word separeted by spaces.

My question is : there is a way to download only the selected message's attachmens ?
I'm parsin the subject of the message and I have to catch only messages that have a specific subject and than download the attachment.
It's possible ?
 

luke2012

Well-Known Member
Licensed User
Longtime User
Thank you for the answer!

Now the code run but when I test it on my google account It find 256 messages.
Whitin this messages I expect to find a mail that I sent to my account (I see the mail in my google mail inbox), but I parsing the subject of all downloaded messages I can't find it.

Which maybe the problem ?
There are particular setting of POP3 settings?
 

luke2012

Well-Known Member
Licensed User
Longtime User
MailParser catch specific subject

Hi to all,
my need is to download an attachment within a mail that have a specific subject.

There is a way to do this using MailParser ?

I saw that the HandlePart Sub download the attachment correct?
CODE : out = File.OpenOutput(dir, filename, False)

There is a way to do this if a specific condition is verified (es. subject = "my subject") ?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
The subject is set during the call to ParseHeaders. You can modify the code:
B4X:
Sub ParseMail (Mail As String, AttachmentsDir As String) As Message
   index = 0
   multipart = False
   boundary = ""
   Dim msg As Message
   msg.Initialize
   msg.Attachments.Initialize
   ParseHeaders(Mail, msg)
        If msg.Subject = "sdfsdf" Then '<------------
   If multipart = False Then
      ParseSimpleBody(Mail, msg)
   Else
      dir = AttachmentsDir
      ParseMultipartBody(Mail, msg)
   End If
        End If
   Return msg
End Sub

Another option is to use DownloadTop and only later download the full message.
 

luke2012

Well-Known Member
Licensed User
Longtime User
Hi Erel,
I did the amendement that you suggested me and now I'm able to catch a specific subject mail.

The problem is that the attachment isn't handle.

I debugged the ParseMail call and when the "HandlePart" sub is called the "Regex.Matcher2" check statment identify that the Headers = Content-Type: text/plain; charset="utf-8" and the file creation is skipped.

The mail contains an attachment that has been created using "SMTP.AddAttachment(File.DirDefaultExternal, Main.DBFileName)" method.

So the result is that the attachment isn't downloaded.
 
Top