From fc9160342969e7c77c547d20ca69b0cb4b659980 Mon Sep 17 00:00:00 2001 From: Daniel Applebaum Date: Thu, 26 Nov 2009 05:10:12 +0000 Subject: [PATCH] Major IMAP IDLE rework: 1) Actual message sync on MessagingController uses same connection as IDLE for faster and more efficient push operation. Uses fewer connections to the server. 2) More aggressive handling of untagged responses should more reliably get flag changes and new messages when many events happen at once. 3) Simplification of new mail notification 4) Push mail now respects the folder visible limit 5) When multiple untagged FETCH responses arrive en bloc, the actual message flags and UIDs are fetched with a single request --- .../android/email/MessagingController.java | 166 ++++++------ .../email/mail/MessageRemovalListener.java | 6 + src/com/android/email/mail/PushReceiver.java | 4 +- .../android/email/mail/store/ImapStore.java | 245 +++++++++++++----- .../android/email/mail/store/LocalStore.java | 172 ++++++------ 5 files changed, 365 insertions(+), 228 deletions(-) create mode 100644 src/com/android/email/mail/MessageRemovalListener.java diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index 90f687fc5..f5d6faee5 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -52,6 +52,7 @@ import com.android.email.mail.FetchProfile; import com.android.email.mail.Flag; import com.android.email.mail.Folder; import com.android.email.mail.Message; +import com.android.email.mail.MessageRemovalListener; import com.android.email.mail.MessageRetrievalListener; import com.android.email.mail.MessagingException; import com.android.email.mail.Part; @@ -1061,7 +1062,7 @@ public class MessagingController implements Runnable /* * Now we download the actual content of messages. */ - int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages); + int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false); setLocalUnreadCountToRemote(localFolder, remoteFolder, newMessages); @@ -1163,22 +1164,14 @@ public class MessagingController implements Runnable } private int downloadMessages(final Account account, final Folder remoteFolder, - final LocalFolder localFolder, List inputMessages) throws MessagingException + final LocalFolder localFolder, List inputMessages, boolean flagSyncOnly) throws MessagingException { final String folder = remoteFolder.getName(); ArrayList syncFlagMessages = new ArrayList(); - ArrayList unsyncedMessages = new ArrayList(); + List unsyncedMessages = new ArrayList(); final AtomicInteger newMessages = new AtomicInteger(0); - int visibleLimit = localFolder.getVisibleLimit(); - int listSize = inputMessages.size(); - - if (listSize > visibleLimit) - { - inputMessages = inputMessages.subList(listSize - visibleLimit, listSize); - } - List messages = new ArrayList(inputMessages); for (Message message : messages) @@ -1189,35 +1182,38 @@ public class MessagingController implements Runnable if (localMessage == null) { - if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) + if (!flagSyncOnly) { - if (Email.DEBUG) + if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { - Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is not downloaded at all"); - } - - unsyncedMessages.add(message); - } - else - { - if (Email.DEBUG) - { - Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded"); - } - // Store the updated message locally - localFolder.appendMessages(new Message[] { message }); - - localMessage = localFolder.getMessage(message.getUid()); - - localMessage.setFlag(Flag.X_DOWNLOADED_FULL, message.isSet(Flag.X_DOWNLOADED_FULL)); - localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, message.isSet(Flag.X_DOWNLOADED_PARTIAL)); - - for (MessagingListener l : getListeners()) - { - l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); - if (!localMessage.isSet(Flag.SEEN)) + if (Email.DEBUG) { - l.synchronizeMailboxNewMessage(account, folder, localMessage); + Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is not downloaded at all"); + } + + unsyncedMessages.add(message); + } + else + { + if (Email.DEBUG) + { + Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded"); + } + // Store the updated message locally + localFolder.appendMessages(new Message[] { message }); + + localMessage = localFolder.getMessage(message.getUid()); + + localMessage.setFlag(Flag.X_DOWNLOADED_FULL, message.isSet(Flag.X_DOWNLOADED_FULL)); + localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, message.isSet(Flag.X_DOWNLOADED_PARTIAL)); + + for (MessagingListener l : getListeners()) + { + l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); + if (!localMessage.isSet(Flag.SEEN)) + { + l.synchronizeMailboxNewMessage(account, folder, localMessage); + } } } } @@ -1251,6 +1247,13 @@ public class MessagingController implements Runnable * fetch results for newest to oldest. If not, no harm done. */ Collections.reverse(unsyncedMessages); + int visibleLimit = localFolder.getVisibleLimit(); + int listSize = unsyncedMessages.size(); + + if (listSize > visibleLimit) + { + unsyncedMessages = unsyncedMessages.subList(listSize - visibleLimit, listSize); + } FetchProfile fp = new FetchProfile(); if (remoteFolder.supportsFetchingFlags()) @@ -1281,9 +1284,9 @@ public class MessagingController implements Runnable // Store the new message locally localFolder.appendMessages(new Message[] - { - message - }); + { + message + }); if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) { @@ -1556,6 +1559,19 @@ public class MessagingController implements Runnable } Log.i(Email.LOG_TAG, "SYNC: Synced remote messages for folder " + folder + ", " + newMessages.get() + " new messages"); + localFolder.purgeToVisibleLimit(new MessageRemovalListener() + { + @Override + public void messageRemoved(Message message) + { + for (MessagingListener l : getListeners()) + { + l.synchronizeMailboxRemovedMessage(account, folder, message); + } + } + + }); + return newMessages.get(); } @@ -2072,7 +2088,7 @@ public class MessagingController implements Runnable } String rootCauseMessage = getRootCauseMessage(t); - log("Error" + "'" + rootCauseMessage + "'"); + Log.e(Email.LOG_TAG, "Error " + "'" + rootCauseMessage + "'", t); Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication); LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName()); @@ -2474,9 +2490,9 @@ public class MessagingController implements Runnable fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localFolder.fetch(new Message[] - { - message - }, fp, null); + { + message + }, fp, null); localFolder.close(false); if (!message.isSet(Flag.SEEN)) { @@ -2753,9 +2769,9 @@ public class MessagingController implements Runnable (LocalFolder) localStore.getFolder(account.getOutboxFolderName()); localFolder.open(OpenMode.READ_WRITE); localFolder.appendMessages(new Message[] - { - message - }); + { + message + }); Message localMessage = localFolder.getMessage(message.getUid()); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localFolder.close(false); @@ -3799,9 +3815,9 @@ public class MessagingController implements Runnable (LocalFolder) localStore.getFolder(account.getDraftsFolderName()); localFolder.open(OpenMode.READ_WRITE); localFolder.appendMessages(new Message[] - { - message - }); + { + message + }); Message localMessage = localFolder.getMessage(message.getUid()); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); @@ -3989,15 +4005,15 @@ public class MessagingController implements Runnable } } - public void messagesFlagsChanged(String folderName, + public void messagesFlagsChanged(Folder folder, List messages) { - controller.messagesArrived(account, folderName, messages, false); + controller.messagesArrived(account, folder, messages, true); } - public void messagesArrived(String folderName, List messages) + public void messagesArrived(Folder folder, List messages) { - controller.messagesArrived(account, folderName, messages, true); + controller.messagesArrived(account, folder, messages, false); } public void sleep(long millis) @@ -4152,44 +4168,37 @@ public class MessagingController implements Runnable } - public void messagesArrived(final Account account, final String folderName, final List messages, final boolean doNotify) + public void messagesArrived(final Account account, final Folder remoteFolder, final List messages, final boolean flagSyncOnly) { Log.i(Email.LOG_TAG, "Got new pushed email messages for account " + account.getDescription() - + ", folder " + folderName); + + ", folder " + remoteFolder.getName()); final CountDownLatch latch = new CountDownLatch(1); putBackground("Push messageArrived of account " + account.getDescription() - + ", folder " + folderName, null, new Runnable() + + ", folder " + remoteFolder.getName(), null, new Runnable() { public void run() { LocalFolder localFolder = null; - Folder remoteFolder = null; try { LocalStore localStore = (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication); - Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication); - remoteFolder = remoteStore.getFolder(folderName); - localFolder= (LocalFolder) localStore.getFolder(folderName); + localFolder= (LocalFolder) localStore.getFolder(remoteFolder.getName()); localFolder.open(OpenMode.READ_WRITE); remoteFolder.open(OpenMode.READ_WRITE); - int newCount = downloadMessages(account, remoteFolder, localFolder, messages); + int newCount = downloadMessages(account, remoteFolder, localFolder, messages, flagSyncOnly); + setLocalUnreadCountToRemote(localFolder, remoteFolder, messages.size()); + localFolder.setLastPush(System.currentTimeMillis()); localFolder.setStatus(null); int unreadMessageCount = account.getUnreadMessageCount(mApplication, mApplication); - if (doNotify && newCount > 0 && unreadMessageCount > 0) - { - notifyAccount(mApplication, account, newCount, unreadMessageCount); - } - if (unreadMessageCount == 0) - { - notifyAccount(mApplication, account, newCount, unreadMessageCount); - } + Log.i(Email.LOG_TAG, "messagesArrived newCount = " + newCount + ", unreadMessageCount = " + unreadMessageCount); + notifyAccount(mApplication, account, newCount, unreadMessageCount); for (MessagingListener l : getListeners()) { - l.folderStatusChanged(account, folderName); + l.folderStatusChanged(account, remoteFolder.getName()); l.accountStatusChanged(account, unreadMessageCount); } @@ -4208,7 +4217,7 @@ public class MessagingController implements Runnable } for (MessagingListener l : getListeners()) { - l.synchronizeMailboxFailed(account, folderName, errorMessage); + l.synchronizeMailboxFailed(account, remoteFolder.getName(), errorMessage); } addErrorMessage(account, e); } @@ -4225,17 +4234,6 @@ public class MessagingController implements Runnable Log.e(Email.LOG_TAG, "Unable to close localFolder", e); } } - if (remoteFolder != null) - { - try - { - remoteFolder.close(false); - } - catch (Exception e) - { - Log.e(Email.LOG_TAG, "Unable to close remoteFolder", e); - } - } latch.countDown(); } @@ -4249,7 +4247,7 @@ public class MessagingController implements Runnable { Log.e(Email.LOG_TAG, "Interrupted while awaiting latch release", e); } - Log.i(Email.LOG_TAG, "Latch released"); + Log.i(Email.LOG_TAG, "MessagingController.messagesArrivedLatch released"); } enum MemorizingState { STARTED, FINISHED, FAILED }; diff --git a/src/com/android/email/mail/MessageRemovalListener.java b/src/com/android/email/mail/MessageRemovalListener.java new file mode 100644 index 000000000..b07ca4f57 --- /dev/null +++ b/src/com/android/email/mail/MessageRemovalListener.java @@ -0,0 +1,6 @@ +package com.android.email.mail; + +public interface MessageRemovalListener +{ + public void messageRemoved(Message message); +} diff --git a/src/com/android/email/mail/PushReceiver.java b/src/com/android/email/mail/PushReceiver.java index a163464d1..904b28586 100644 --- a/src/com/android/email/mail/PushReceiver.java +++ b/src/com/android/email/mail/PushReceiver.java @@ -6,8 +6,8 @@ public interface PushReceiver { public void acquireWakeLock(); public void releaseWakeLock(); - public void messagesArrived(String folderName, List mess); - public void messagesFlagsChanged(String folderName, List mess); + public void messagesArrived(Folder folder, List mess); + public void messagesFlagsChanged(Folder folder, List mess); public String getPushState(String folderName); public void pushError(String errorMessage, Exception e); public void setPushActive(String folderName, boolean enabled); diff --git a/src/com/android/email/mail/store/ImapStore.java b/src/com/android/email/mail/store/ImapStore.java index 0c55a1bb3..082cb2bcb 100644 --- a/src/com/android/email/mail/store/ImapStore.java +++ b/src/com/android/email/mail/store/ImapStore.java @@ -464,6 +464,12 @@ public class ImapStore extends Store public void open(OpenMode mode) throws MessagingException { internalOpen(mode); + + if (mMessageCount == -1) + { + throw new MessagingException( + "Did not find message count during open"); + } } public List internalOpen(OpenMode mode) throws MessagingException @@ -559,13 +565,8 @@ public class ImapStore extends Store } } - if (mMessageCount == -1) - { - throw new MessagingException( - "Did not find message count with command '" + command + "'"); - } mExists = true; - return null; + return responses; } catch (IOException ioe) { @@ -594,7 +595,7 @@ public class ImapStore extends Store { if (mMessageCount != -1) { - // close(); + // close(); mMessageCount = -1; } if (!isOpen()) @@ -788,12 +789,12 @@ public class ImapStore extends Store public Message[] getMessages(int start, int end, MessageRetrievalListener listener) throws MessagingException { - - return getMessages(start, end, false, listener); } - protected Message[] getMessages(int start, int end, boolean includeDeleted, MessageRetrievalListener listener) + + + protected Message[] getMessages(final int start, final int end, final boolean includeDeleted, final MessageRetrievalListener listener) throws MessagingException { if (start < 1 || end < 1 || end < start) @@ -802,22 +803,48 @@ public class ImapStore extends Store String.format("Invalid message set %d %d", start, end)); } + ImapSearcher searcher = new ImapSearcher() + { + public List search() throws IOException, MessagingException + { + return executeSimpleCommand(String.format("UID SEARCH %d:%d" + (includeDeleted ? "" : " NOT DELETED"), start, end)); + } + }; + return search(searcher, listener); + + } + protected Message[] getMessages(final List mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener) + throws MessagingException + { + ImapSearcher searcher = new ImapSearcher() + { + public List search() throws IOException, MessagingException + { + return executeSimpleCommand(String.format("UID SEARCH %s" + (includeDeleted ? "" : " NOT DELETED"), Utility.combine(mesgSeqs.toArray(), ','))); + } + }; + return search(searcher, listener); + } + + private Message[] search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException + { + checkOpen(); ArrayList messages = new ArrayList(); try { boolean gotSearchValues = false; ArrayList uids = new ArrayList(); - List responses = executeSimpleCommand(String.format("UID SEARCH %d:%d" + (includeDeleted ? "" : " NOT DELETED"), start, end)); + List responses = searcher.search(); // for (ImapResponse response : responses) { - // Log.d(Email.LOG_TAG, "Got search response: " + response.get(0) + ", size " + response.size()); + // Log.d(Email.LOG_TAG, "Got search response: " + response.get(0) + ", size " + response.size()); if (response.get(0).equals("SEARCH")) { gotSearchValues = true; for (int i = 1, count = response.size(); i < count; i++) { - // Log.d(Email.LOG_TAG, "Got search response UID: " + response.getString(i)); + // Log.d(Email.LOG_TAG, "Got search response UID: " + response.getString(i)); uids.add(Integer.parseInt(response.getString(i))); } @@ -975,7 +1002,6 @@ public class ImapStore extends Store do { response = mConnection.readResponse(); - handleUntaggedResponse(response); if (Email.DEBUG) { Log.v(Email.LOG_TAG, "response for fetch: " + response + " for " + getLogId()); @@ -988,7 +1014,8 @@ public class ImapStore extends Store Message message = messageMap.get(uid); if (message == null) { - Log.w(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId()); + Log.d(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId()); + handleUntaggedResponse(response); continue; } if (listener != null) @@ -1107,6 +1134,10 @@ public class ImapStore extends Store listener.messageFinished(message, messageNumber, messageMap.size()); } } + else + { + handleUntaggedResponse(response); + } while (response.more()); @@ -1467,9 +1498,9 @@ public class ImapStore extends Store try { /* - * Try to find the UID of the message we just appended using the - * Message-ID header. - */ + * Try to find the UID of the message we just appended using the + * Message-ID header. + */ String[] messageIdHeader = message.getHeader("Message-ID"); if (messageIdHeader == null || messageIdHeader.length == 0) @@ -1743,9 +1774,9 @@ public class ImapStore extends Store SSLContext sslContext = SSLContext.getInstance("TLS"); final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; sslContext.init(null, new TrustManager[] - { - TrustManagerFactory.get(mHost, secure) - }, new SecureRandom()); + { + TrustManagerFactory.get(mHost, secure) + }, new SecureRandom()); mSocket = sslContext.getSocketFactory().createSocket(); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); } @@ -1783,10 +1814,10 @@ public class ImapStore extends Store { if (capability instanceof String) { - if (Email.DEBUG) - { - Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for " + getLogId()); - } +// if (Email.DEBUG) +// { +// Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for " + getLogId()); +// } capabilities.add((String)capability); } } @@ -1807,9 +1838,9 @@ public class ImapStore extends Store SSLContext sslContext = SSLContext.getInstance("TLS"); boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED; sslContext.init(null, new TrustManager[] - { - TrustManagerFactory.get(mHost, secure) - }, new SecureRandom()); + { + TrustManagerFactory.get(mHost, secure) + }, new SecureRandom()); mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort, true); mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT); @@ -1886,10 +1917,10 @@ public class ImapStore extends Store if (Email.DEBUG) { Log.v(Email.LOG_TAG, "Connection " + getLogId() + " has " + capabilities.size() + " capabilities"); - for (String capability : capabilities) - { - Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId()); - } +// for (String capability : capabilities) +// { +// Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId()); +// } } return capabilities.contains("IDLE"); } @@ -2223,6 +2254,7 @@ public class ImapStore extends Store final AtomicBoolean idling = new AtomicBoolean(false); final AtomicBoolean doneSent = new AtomicBoolean(false); final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME); + List storedUntaggedResponses = new ArrayList(); public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) { @@ -2265,6 +2297,7 @@ public class ImapStore extends Store Log.i(Email.LOG_TAG, "Pusher starting for " + getLogId()); while (stop.get() != true) { + try { int oldUidNext = -1; @@ -2328,14 +2361,25 @@ public class ImapStore extends Store { if (stop.get() != true) { - Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId()); + List untaggedResponses = null; + if (storedUntaggedResponses.size() > 0) + { + Log.i(Email.LOG_TAG, "Processing " + storedUntaggedResponses.size() + " from previous commands for " + getLogId()); + untaggedResponses = new ArrayList(storedUntaggedResponses); + } + else + { + Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId()); - receiver.setPushActive(getName(), true); - idling.set(true); - doneSent.set(false); - executeSimpleCommand("IDLE", false, ImapFolderPusher.this); - idling.set(false); - receiver.setPushActive(getName(), false); + receiver.setPushActive(getName(), true); + idling.set(true); + doneSent.set(false); + untaggedResponses = executeSimpleCommand("IDLE", false, ImapFolderPusher.this); + idling.set(false); + + } + storedUntaggedResponses.clear(); + processUntaggedResponses(untaggedResponses); delayTime.set(NORMAL_DELAY_TIME); } } @@ -2343,6 +2387,7 @@ public class ImapStore extends Store catch (Exception e) { receiver.acquireWakeLock(); + storedUntaggedResponses.clear(); idling.set(false); receiver.setPushActive(getName(), false); try @@ -2372,6 +2417,7 @@ public class ImapStore extends Store } } } + receiver.setPushActive(getName(), false); try { Log.i(Email.LOG_TAG, "Pusher for " + getLogId() + " is exiting"); @@ -2391,39 +2437,50 @@ public class ImapStore extends Store listeningThread.start(); } - List flagSyncMsgSeqs = new ArrayList(); - - protected List handleUntaggedResponses(List responses) + @Override + protected void handleUntaggedResponse(ImapResponse response) { - flagSyncMsgSeqs.clear(); - int oldMessageCount = mMessageCount; - - super.handleUntaggedResponses(responses); - - List flagSyncMsgSeqsCopy = new ArrayList(); - flagSyncMsgSeqsCopy.addAll(flagSyncMsgSeqs); - - - if (Email.DEBUG) + if (response.mTag == null && response.size() > 1) { - Log.d(Email.LOG_TAG, "oldMessageCount = " + oldMessageCount + ", new mMessageCount = " + mMessageCount - + " for " + getLogId()); + Object responseType = response.get(1); + if ("FETCH".equals(responseType) + || "EXPUNGE".equals(responseType) + || "EXISTS".equals(responseType)) + { + if (Email.DEBUG) + { + Log.d(Email.LOG_TAG, "Storing response " + response + " for later processing"); + } + storedUntaggedResponses.add(response); + } } - if (oldMessageCount > 0 && mMessageCount > oldMessageCount) + } + + protected void processUntaggedResponses(List responses) + { + int oldMessageCount = mMessageCount; + List flagSyncMsgSeqs = new ArrayList(); + + for (ImapResponse response : responses) + { + oldMessageCount += processUntaggedResponse(oldMessageCount, response, flagSyncMsgSeqs); + } + if (oldMessageCount < 0) + { + oldMessageCount = 0; + } + if (mMessageCount > oldMessageCount) { syncMessages(oldMessageCount + 1, mMessageCount, true); } if (Email.DEBUG) { - Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqsCopy + " messages needing flag sync for " + getLogId()); + Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqs + " messages needing flag sync for " + getLogId()); } - // TODO: Identify ranges and call syncMessages on said identified ranges - for (Integer msgSeq : flagSyncMsgSeqsCopy) + if (flagSyncMsgSeqs.size() > 0) { - syncMessages(msgSeq, msgSeq, false); + syncMessages(flagSyncMsgSeqs); } - - return responses; } private void syncMessages(int start, int end, boolean newArrivals) @@ -2448,9 +2505,32 @@ public class ImapStore extends Store } } - protected void handleUntaggedResponse(ImapResponse response) + private void syncMessages(List flagSyncMsgSeqs) + { + try + { + Message[] messageArray = null; + + messageArray = getMessages(flagSyncMsgSeqs, true, null); + + List messages = new ArrayList(); + for (Message message : messageArray) + { + messages.add(message); + } + pushMessages(messages, false); + + } + catch (Exception e) + { + receiver.pushError("Exception while processing Push untagged responses", e); + } + } + + protected int processUntaggedResponse(int oldMessageCount, ImapResponse response, List flagSyncMsgSeqs) { super.handleUntaggedResponse(response); + int messageCountDelta = 0; if (response.mTag == null && response.size() > 1) { try @@ -2463,7 +2543,37 @@ public class ImapStore extends Store { Log.d(Email.LOG_TAG, "Got untagged FETCH for msgseq " + msgSeq + " for " + getLogId()); } - flagSyncMsgSeqs.add(msgSeq); + if (flagSyncMsgSeqs.contains(msgSeq) == false) + { + flagSyncMsgSeqs.add(msgSeq); + } + } + if ("EXPUNGE".equals(responseType)) + { + int msgSeq = response.getNumber(0); + if (msgSeq <= oldMessageCount) + { + messageCountDelta = -1; + } + if (Email.DEBUG) + { + Log.d(Email.LOG_TAG, "Got untagged EXPUNGE for msgseq " + msgSeq + " for " + getLogId()); + } + List newSeqs = new ArrayList(); + Iterator flagIter = flagSyncMsgSeqs.iterator(); + while (flagIter.hasNext()) + { + Integer flagMsg = flagIter.next(); + if (flagMsg >= msgSeq) + { + flagIter.remove(); + if (flagMsg > msgSeq) + { + newSeqs.add(flagMsg--); + } + } + } + flagSyncMsgSeqs.addAll(newSeqs); } } catch (Exception e) @@ -2471,6 +2581,7 @@ public class ImapStore extends Store Log.e(Email.LOG_TAG, "Could not handle untagged FETCH for " + getLogId(), e); } } + return messageCountDelta; } @@ -2481,11 +2592,11 @@ public class ImapStore extends Store { if (newArrivals) { - receiver.messagesArrived(getName(), messages); + receiver.messagesArrived(this, messages); } else { - receiver.messagesFlagsChanged(getName(), messages); + receiver.messagesFlagsChanged(this, messages); } } catch (RuntimeException e) @@ -2684,4 +2795,8 @@ public class ImapStore extends Store } } + private interface ImapSearcher + { + List search() throws IOException, MessagingException; + } } diff --git a/src/com/android/email/mail/store/LocalStore.java b/src/com/android/email/mail/store/LocalStore.java index c7007da50..8b609d0ab 100644 --- a/src/com/android/email/mail/store/LocalStore.java +++ b/src/com/android/email/mail/store/LocalStore.java @@ -46,6 +46,7 @@ import com.android.email.mail.FetchProfile; import com.android.email.mail.Flag; import com.android.email.mail.Folder; import com.android.email.mail.Message; +import com.android.email.mail.MessageRemovalListener; import com.android.email.mail.MessageRetrievalListener; import com.android.email.mail.MessagingException; import com.android.email.mail.Part; @@ -610,9 +611,9 @@ public class LocalStore extends Store implements Serializable cursor = mDb.rawQuery("SELECT id, unread_count, visible_limit, last_updated, status, push_state, last_pushed FROM folders " + "where folders.name = ?", new String[] - { - mName - }); + { + mName + }); if (cursor.moveToFirst()) { @@ -705,10 +706,10 @@ public class LocalStore extends Store implements Serializable throw new MessagingException("Folder " + mName + " already exists."); } mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] - { - mName, - Email.DEFAULT_VISIBLE_LIMIT - }); + { + mName, + Email.DEFAULT_VISIBLE_LIMIT + }); return true; } @@ -719,10 +720,10 @@ public class LocalStore extends Store implements Serializable throw new MessagingException("Folder " + mName + " already exists."); } mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] - { - mName, - visibleLimit - }); + { + mName, + visibleLimit + }); return true; } @@ -745,9 +746,9 @@ public class LocalStore extends Store implements Serializable { cursor = mDb.rawQuery("SELECT COUNT(*) FROM messages WHERE messages.folder_id = ?", new String[] - { - Long.toString(mFolderId) - }); + { + Long.toString(mFolderId) + }); cursor.moveToFirst(); int messageCount = cursor.getInt(0); return messageCount; @@ -799,6 +800,23 @@ public class LocalStore extends Store implements Serializable return mVisibleLimit; } + public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException + { + open(OpenMode.READ_WRITE); + Message[] messages = getMessages(null); + for (int i = 0; i < messages.length; i++) + { + if (i >= mVisibleLimit) + { + if (listener != null) + { + listener.messageRemoved(messages[i]); + } + messages[i].setFlag(Flag.X_DESTROYED, true); + } + } + } + public void setVisibleLimit(int visibleLimit) throws MessagingException { @@ -1060,19 +1078,19 @@ public class LocalStore extends Store implements Serializable cursor = mDb.query( "attachments", new String[] - { - "id", - "size", - "name", - "mime_type", - "store_data", - "content_uri" - }, - "message_id = ?", - new String[] { Long.toString(localMessage.mId) }, - null, - null, - null); + { + "id", + "size", + "name", + "mime_type", + "store_data", + "content_uri" + }, + "message_id = ?", + new String[] { Long.toString(localMessage.mId) }, + null, + null, + null); while (cursor.moveToNext()) { @@ -1227,9 +1245,9 @@ public class LocalStore extends Store implements Serializable + "bcc_list, reply_to_list, attachment_count, internal_date, message_id " + "FROM messages " + "WHERE uid = ? " + "AND folder_id = ?", new String[] - { - message.getUid(), Long.toString(mFolderId) - }); + { + message.getUid(), Long.toString(mFolderId) + }); if (!cursor.moveToNext()) { return null; @@ -1272,9 +1290,9 @@ public class LocalStore extends Store implements Serializable + (includeDeleted ? "" : "deleted = 0") + " folder_id = ? ORDER BY date DESC" , new String[] - { - Long.toString(mFolderId) - }); + { + Long.toString(mFolderId) + }); int i = 0; @@ -1369,11 +1387,11 @@ public class LocalStore extends Store implements Serializable message.setUid(Email.LOCAL_UID_PREFIX + UUID.randomUUID().toString()); mDb.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] - { - lDestFolder.getId(), - message.getUid(), - lMessage.getId() - }); + { + lDestFolder.getId(), + message.getUid(), + lMessage.getId() + }); LocalMessage placeHolder = new LocalMessage(oldUID, this); placeHolder.setFlagInternal(Flag.DELETED, true); @@ -1561,27 +1579,27 @@ public class LocalStore extends Store implements Serializable + "html_content = ?, text_content = ?, reply_to_list = ?, " + "attachment_count = ? WHERE id = ?", new Object[] - { - message.getUid(), - message.getSubject(), - Address.pack(message.getFrom()), - message.getSentDate() == null ? System - .currentTimeMillis() : message.getSentDate() - .getTime(), - Utility.combine(message.getFlags(), ',').toUpperCase(), - mFolderId, - Address.pack(message - .getRecipients(RecipientType.TO)), - Address.pack(message - .getRecipients(RecipientType.CC)), - Address.pack(message - .getRecipients(RecipientType.BCC)), - html.length() > 0 ? html : null, - text.length() > 0 ? text : null, - Address.pack(message.getReplyTo()), - attachments.size(), - message.mId - }); + { + message.getUid(), + message.getSubject(), + Address.pack(message.getFrom()), + message.getSentDate() == null ? System + .currentTimeMillis() : message.getSentDate() + .getTime(), + Utility.combine(message.getFlags(), ',').toUpperCase(), + mFolderId, + Address.pack(message + .getRecipients(RecipientType.TO)), + Address.pack(message + .getRecipients(RecipientType.CC)), + Address.pack(message + .getRecipients(RecipientType.BCC)), + html.length() > 0 ? html : null, + text.length() > 0 ? text : null, + Address.pack(message.getReplyTo()), + attachments.size(), + message.mId + }); for (int i = 0, count = attachments.size(); i < count; i++) { @@ -1621,9 +1639,9 @@ public class LocalStore extends Store implements Serializable { mDb.execSQL("DELETE FROM headers WHERE id = ?", new Object[] - { - id - }); + { + id + }); } /** @@ -1802,9 +1820,9 @@ public class LocalStore extends Store implements Serializable { open(OpenMode.READ_ONLY); mDb.execSQL("DELETE FROM messages WHERE folder_id = ? and date < ?", new Object[] - { - Long.toString(mFolderId), new Long(cutoff) - }); + { + Long.toString(mFolderId), new Long(cutoff) + }); resetUnreadCount(); } @@ -1840,9 +1858,9 @@ public class LocalStore extends Store implements Serializable deleteAttachments(message.getUid()); } mDb.execSQL("DELETE FROM folders WHERE id = ?", new Object[] - { - Long.toString(mFolderId), - }); + { + Long.toString(mFolderId), + }); } @Override @@ -2158,18 +2176,18 @@ public class LocalStore extends Store implements Serializable "reply_to_list = NULL " + "WHERE id = ?", new Object[] - { - mId - }); + { + mId + }); /* * Delete all of the messages' attachments to save space. */ mDb.execSQL("DELETE FROM attachments WHERE id = ?", new Object[] - { - mId - }); + { + mId + }); ((LocalFolder)mFolder).deleteHeaders(mId); @@ -2213,9 +2231,9 @@ public class LocalStore extends Store implements Serializable * Set the flags on the message. */ mDb.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?", new Object[] - { - Utility.combine(getFlags(), ',').toUpperCase(), mId - }); + { + Utility.combine(getFlags(), ',').toUpperCase(), mId + }); } }