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.