1

I'm currently developing an email application with a background service used in conjunction with the JavMail Idle functionality. The service stays running perfectly in the background (even when the app is closed) and then I've got a thread running every 5 minutes to keep the server connection alive to allow the idle (Push support) functionality to work.

Whilst this is currently working great and emails will come through automatically at any time of the day, doing this is using a lot of battery and data but when I set the thread to interrupt at 15, 20 or 30 minutes the app stops automatically receiving emails but checking the Logcat, I can't see any exception being thrown for a FolderClosedException or anything to do with a timeout.

My code can be seen below:

public void checkInboxEmail(final String host, final String user, final String password) {

    Log.d(TAG, "checkEmail");

    this.host = host;
    this.user = user;
    this.password = password;

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Log.d(TAG, "checkEmail - run()");

                long databaseRecords;

                //create properties field
                Properties properties = new Properties();
                properties.put("mail.store.protocol", "imaps");
                properties.put("mail.imaps.ssl.trust", "*");

                emailSession = Session.getInstance(properties, new Authenticator() {
                    protected PasswordAuthentication getPasswordAuthentication() {
                        return new PasswordAuthentication(user, password);
                    }
                });


                IMAPStore imapStore = (IMAPStore) emailSession.getStore("imaps");
                // imapStore.connect();

                imapStore.connect(host, user, password);

                if (imapStore.isConnected()) {
                    Log.d("MailPush", "Successfully connected to IMAP");
                } else {
                    Log.d("MailPush", "Not connected to IMAP");
                }

                final IMAPFolder folder = (IMAPFolder) imapStore.getFolder("Inbox");
                folder.open(IMAPFolder.READ_WRITE);

                databaseRecords = dbManager.getReceivedEmailRecordsCount();

                if (databaseRecords < folder.getMessageCount()) {
                    Log.d(TAG, "Receiving Mail...");
                    receiveMail(folder.getMessages());
                } else {
                    Log.d(TAG, "Records match.");
                }

                Folder[] fdr = imapStore.getDefaultFolder().list();
                for (Folder fd : fdr)
                    System.out.println(">> " + fd.getName());

                folder.addMessageCountListener(new MessageCountListener() {

                    public void messagesAdded(MessageCountEvent e) {

                        System.out.println("Message Added Event Fired");
                        Log.d(TAG, "MESSAGE TYPE: " + e.getType());
                        //ADDED = 1 & REMOVED = 2

                        try {
                            Message[] messages = e.getMessages();

                            System.out.println("messages.length---" + messages.length);

                            for (Message message : messages) {
                                if (!message.getFlags().contains(Flags.Flag.SEEN)) {

                                    //Message is new (hasn't been seen) > Message Details
                                    System.out.println("---------------------------------");
                                    System.out.println("Email Number " + (message.getMessageNumber()));
                                    System.out.println("Subject: " + message.getSubject());
                                    System.out.println("From: " + message.getFrom()[0]);
                                    System.out.println("Text: " + message.getContent().toString());

                                    String from = message.getFrom()[0].toString();

                                    String cc = InternetAddress.toString(message.getRecipients(Message.RecipientType.CC));
                                    Log.d(TAG, "CC 1: " + cc);

                                    Address[] recipients = message.getRecipients(Message.RecipientType.CC);
                                    cc = InternetAddress.toString(recipients);
                                    Log.d(TAG, "CC 2: " + cc);

                                    //Check Encryption Details > Add SEEN Flag > Add to database
                                    checkEncryption((MimeMessage) message, from, cc);
                                }
                            }
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }

                    public void messagesRemoved(MessageCountEvent e) {
                        System.out.println("Message Removed Event fired");
                    }
                });

                folder.addMessageChangedListener(new MessageChangedListener() {

                    public void messageChanged(MessageChangedEvent e) {
                        System.out.println("Message Changed Event fired");
                    }
                });

                startListening(folder);

                //close the store and folder objects
                //   emailFolder.close(false);
                //   store.close();

            } catch (NoSuchProviderException e) {
                e.printStackTrace();
            } catch (MessagingException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

private void startListening(IMAPFolder imapFolder) {
    Log.d(TAG, "startListening");

    // We need to create a new thread to keep alive the connection
    Thread t = new Thread(
            new KeepAliveRunnable(imapFolder), "IdleConnectionKeepAlive"
    );

    t.start();

    while (!Thread.interrupted()) {
        Log.d(TAG, "Starting IDLE");
        try {
            Log.d(TAG, "Setting IDLE");
            imapFolder.idle();
        } catch (FolderClosedException fex) {
            //Server closes connection.
            Log.d(TAG, "FolderClosedException. Server potentially dropped connection. Retrying connection...");
            fex.printStackTrace();

            if (!isServiceRunning(MyService.class)) {
                Log.d(TAG, "Service isn't running. Starting service...");

                //Start service
                Intent intent = new Intent(context, MyService.class);
                intent.putExtra("host", host);
                intent.putExtra("email", user);
                intent.putExtra("password", password);

                context.startService(intent);
            } else {
                Log.d(TAG, "Service is already running. Checking email...");
                checkInboxEmail(host, user, password);
            }
        } catch (MessagingException e) {
            //Idle isn't supported by server.
            Log.d(TAG, "Messaging exception during IDLE: ");
            e.printStackTrace();
        }
    }

    // Shutdown keep alive thread
    if (t.isAlive()) {
        Log.d(TAG, "Interrupting thread");
        t.interrupt();
    }
}

private static class KeepAliveRunnable implements Runnable {

    private final String TAG = getClass().getName();

    private static final long KEEP_ALIVE_FREQ = 60000 * 10; // 15 minutes (Exchange connection drops after 20-30 minutes)

    private IMAPFolder folder;

    KeepAliveRunnable(IMAPFolder folder) {
        this.folder = folder;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                Thread.sleep(KEEP_ALIVE_FREQ);

                // Perform a NOOP just to keep alive the connection
                Log.d(TAG, "Performing a NOOP to keep the connection alive");
                folder.doCommand(new IMAPFolder.ProtocolCommand() {
                    public Object doCommand(IMAPProtocol p)
                            throws ProtocolException {
                        p.simpleCommand("NOOP", null);
                        return null;
                    }
                });
            } catch (InterruptedException e) {
                // Ignore, just aborting the thread...
                Log.d(TAG, "Interrupted...");
                e.printStackTrace();
            } catch (MessagingException e) {
                // Shouldn't really happen...
                Log.d(TAG, "Unexpected exception while keeping alive the IDLE connection");
                e.printStackTrace();
            }
        }
    }
}

private void receiveMail(Message[] messages) {
    try {

        System.out.println("messages.length---" + messages.length);

        for (Message message : messages) {
            if (!message.getFlags().contains(Flags.Flag.SEEN)) {

                //Message is new (hasn't been seen) > Message Details
                System.out.println("---------------------------------");
                System.out.println("Email Number " + (message.getMessageNumber()));
                System.out.println("Subject: " + message.getSubject());
                System.out.println("From: " + message.getFrom()[0]);
                System.out.println("Text: " + message.getContent().toString());

                String from = message.getFrom()[0].toString();
                String cc = InternetAddress.toString(message.getRecipients(Message.RecipientType.CC));

                //Check Encryption Details > Add SEEN Flag > Add to database
                checkEncryption((MimeMessage) message, from, cc);
            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

The idea to use the noop command came from here: JavaMail: Keeping IMAPFolder.idle() alive

Therefore, I am asking how can the code that I have shared above, be improved / optimised yet always remain connected through the idle functionality? The server in question is a Microsoft Exchange server so it would likely be beneficial to know a rough timeout estimate to issue the noop command, in order to keep the connection alive or any other suggestions for keeping the server connection alive through idle to allow the application to virtually use 'Push Support', would be gratefully appreciated.

Community
  • 1
  • 1
Toby Clench
  • 403
  • 1
  • 5
  • 22
  • The NOOP should work, but you can make your code simpler by just using folder.getMessageCount(), which will have the same effect. Try turning on [JavaMail debug output](http://www.oracle.com/technetwork/java/javamail/faq/index.html#debug) to see what's happening when the server times out the connection and your keep alive code continues to run. Maybe your keep alive code is getting an unexpected RuntimeException and terminating? – Bill Shannon Jan 14 '17 at 22:29
  • You probably also need to set [mail.imaps.timeout](https://javamail.java.net/nonav/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.timeout) and maybe [maip.imaps.writetimeout](https://javamail.java.net/nonav/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.writetimeout) and [mail.imaps.connectiontimeout](https://javamail.java.net/nonav/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.connectiontimeout). – Bill Shannon Jan 14 '17 at 22:31
  • @BillShannon, thanks for these suggestions. I will make the relevant change to use folder.getMessageCount() instead and will now debug the application by implementing `properties.put("mail.debug", "true");` to the session. As for the timeout, writetimout and connectiontimeout, the documentation says that their default values are infinite. What should I be setting these to if I want the 'KEEP_ALIVE_FREQ' (in KeepAliveRunnable) set to 15 or 29 minutes? Thanks – Toby Clench Jan 15 '17 at 16:26
  • If the read times out while it's waiting for IDLE, it will just ignore it and restart the read, so you can set the timeouts to whatever is reasonable for your mail server. – Bill Shannon Jan 15 '17 at 21:29
  • Those timeout doc links are now: [mail.imap.timeout](https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.timeout) [mail.imap.writetimeout](https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.writetimeout) and [mail.imap.connectiontimeout](https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#mail.imap.connectiontimeout) – seanf May 10 '21 at 07:11

0 Answers0