From 6c84e196aa9ddd37f33c57bcb189f9e3cca590a3 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 21 Jun 2011 02:56:53 +0530 Subject: [PATCH 01/80] Astyle is seriously getting on my nerves. I'm committing this with nothing but astyle changes so that forthcoming commits are clean. --- .../k9/activity/setup/AccountSettings.java | 4 ++-- src/com/fsck/k9/mail/internet/MimeUtility.java | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index 38c9e38e0..1032291f6 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -884,8 +884,8 @@ public class AccountSettings extends K9PreferenceActivity { } } - allFolderValues = new String[folders.size()+1]; - allFolderLabels = new String[folders.size()+1]; + allFolderValues = new String[folders.size() + 1]; + allFolderLabels = new String[folders.size() + 1]; allFolderValues[0] = K9.FOLDER_NONE; allFolderLabels[0] = K9.FOLDER_NONE; diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 0fd320c0a..54040a364 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -1076,7 +1076,7 @@ public class MimeUtility { */ if (contentTransferEncoding != null) { contentTransferEncoding = - MimeUtility.getHeaderParameter(contentTransferEncoding, null); + MimeUtility.getHeaderParameter(contentTransferEncoding, null); if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) { in = new QuotedPrintableInputStream(in); } else if ("base64".equalsIgnoreCase(contentTransferEncoding)) { @@ -1102,7 +1102,7 @@ public class MimeUtility { * @throws MessagingException */ public static void collectParts(Part part, ArrayList viewables, - ArrayList attachments) throws MessagingException { + ArrayList attachments) throws MessagingException { /* * If the part is Multipart but not alternative it's either mixed or * something we don't know about, which means we treat it as mixed @@ -1326,10 +1326,10 @@ public class MimeUtility { private static String getJisVariantFromAddress(String address) { if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") || - isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) + isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) return "docomo"; else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") || - isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp")) + isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp")) return "softbank"; else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp")) return "kddi"; @@ -1364,14 +1364,14 @@ public class MimeUtility { // iso-2022-jp variants are supported by no versions as of Dec 2010. if (charset.length() > 19 && charset.startsWith("x-") && - charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) { + charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) { in = new Iso2022JpToShiftJisInputStream(in); charset = "x-" + charset.substring(2, charset.length() - 17) + "-shift_jis-2007"; } // shift_jis variants are supported by Eclair and later. if (charset.length() > 17 && charset.startsWith("x-") && - charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) { + charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) { // If the JIS variant is iPhone, map the Unicode private use area in iPhone to the one in Android after // converting the character set from the standard Shift JIS to Unicode. if (charset.substring(2, charset.length() - 15).equals("iphone")) @@ -1392,7 +1392,7 @@ public class MimeUtility { } if (!supported) { Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset + - ". Falling back to US-ASCII"); + ". Falling back to US-ASCII"); charset = "US-ASCII"; } /* @@ -2393,12 +2393,12 @@ public class MimeUtility { public static void setCharset(String charset, Part part) throws MessagingException { part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, - part.getMimeType() + ";\n charset=" + getExternalCharset(charset)); + part.getMimeType() + ";\n charset=" + getExternalCharset(charset)); } public static String getExternalCharset(String charset) { if (charset.length() > 17 && charset.startsWith("x-") && - charset.endsWith("-shift_jis-2007")) + charset.endsWith("-shift_jis-2007")) return "shift_jis"; return charset; From 970271dbf943e6a71dcb262832c2eaff5880b7cb Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 21 Jun 2011 04:34:57 +0530 Subject: [PATCH 02/80] If the response for an APPEND command contains the APPENDUID response code, read the UID of the newly appended message from there. --- src/com/fsck/k9/mail/store/ImapStore.java | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index b639a11f2..f37e44de8 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -52,6 +52,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.PowerManager; +import android.text.TextUtils; import android.util.Log; import com.beetstra.jutf7.CharsetProvider; @@ -1617,6 +1618,31 @@ public class ImapStore extends Store { while (response.more()); } while (response.mTag == null); + /* + * If the server supports UIDPLUS, then along with the APPEND response it will return an APPENDUID response code. + * e.g. 11 OK [APPENDUID 2 238268] APPEND completed + * + * We can use the UID included in this response to update our records. + * + * Code imported from AOSP Email as of git rev a5c3744a247e432acf9f571a9dfb55321c4baa1a + */ + Object responseList = response.get(1); + + if (responseList instanceof ImapList) { + final ImapList appendList = (ImapList) responseList; + if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { + String serverUid = appendList.getString(2); + if (!TextUtils.isEmpty(serverUid)) { + message.setUid(serverUid); + continue; + } + } + } + + + /* + * This part is executed in case the server does not support UIDPLUS or does not implement the APPENDUID response code. + */ String newUid = getUidFromMessageId(message); if (K9.DEBUG) Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); From 0ba7f206225d4f9c71e83cf94f1bf74ca27aa5aa Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Fri, 24 Jun 2011 02:54:12 +0530 Subject: [PATCH 03/80] Attempt to implement COPYUID, works for the most part except for updation of the LocalStore with freshly copied messages. --- src/com/fsck/k9/mail/store/ImapStore.java | 130 ++++++++++++++++++++-- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index f37e44de8..8824e0988 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -29,6 +29,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -39,7 +40,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import java.util.StringTokenizer; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -813,8 +816,24 @@ public class ImapStore extends Store { ImapFolder iFolder = (ImapFolder)folder; checkOpen(); + + SortedSet messageSet = new TreeSet(new Comparator() { + public int compare(Message m1, Message m2) { + int uid1 = Integer.parseInt(m1.getUid()), uid2 = Integer.parseInt(m2.getUid()); + if (uid1 < uid2) { + return -1; + } else if (uid1 == uid2) { + return 0; + } else { + return 1; + } + } + }); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { + messageSet.add(messages[i]); + + // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); } try { @@ -829,20 +848,104 @@ public class ImapStore extends Store { iFolder.create(FolderType.HOLDS_MESSAGES); } - if (exists(remoteDestName)) { - executeSimpleCommand(String.format("UID COPY %s %s", - Utility.combine(uids, ','), - remoteDestName)); - } else { - throw new MessagingException("IMAPMessage.copyMessages: remote destination folder " + folder.getName() - + " does not exist and could not be created for " + getLogId() - , true); + mConnection.sendCommand(String.format("UID COPY %s %s", + Utility.combine(uids, ','), + remoteDestName), false); + ImapResponse response; + do { + response = mConnection.readResponse(); + handleUntaggedResponse(response); + if (response.mCommandContinuationRequested) { + EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(mConnection.mOut); + eolOut.write('\r'); + eolOut.write('\n'); + eolOut.flush(); + } + while (response.more()); + } while (response.mTag == null); + + /* + * If the server supports UIDPLUS, then along with the COPY response it will return an COPYUID response code. + * e.g. 24 OK [COPYUID 38505 304,319:320 3956:3958] Success + * + * COPYUID is followed by UIDVALIDITY, set of UIDs of copied messages from the source folder and set of corresponding UIDs assigned to them in the destination folder. + * + * We can use the new UIDs included in this response to update our records. + */ + + Object responseList = response.get(1); + + if (responseList instanceof ImapList) { + final ImapList copyList = (ImapList) responseList; + if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { + List oldUids = parseSequenceSet(copyList.getString(2)); + List newUids = parseSequenceSet(copyList.getString(3)); + if (oldUids.size() == newUids.size()) { + Iterator messageIterator = messageSet.iterator(); + for (int i = 0; i < messages.length && messageIterator.hasNext(); i++) { + Message nextMessage = messageIterator.next(); + if (oldUids.get(i).equals(nextMessage.getUid())) { + /* + * Here, we need to *create* new messages in the localstore, same as the older messages, the only changes are that old UIDs need to be swapped with new UIDs, + * and old folder swapped with new folder. + */ +// nextMessage.setUid(newUids.get(i)); + } + } + } + } } } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } } + private List parseSequenceSet(String set) { + int index = 0; + List sequenceList = new ArrayList(); + String element = ""; + + while (index < set.length()) { + if (set.charAt(index) == ':') { + String upperBound = ""; + index++; + while (index < set.length()) { + if (!(set.charAt(index) == ',')) { + upperBound += set.charAt(index); + index++; + } else { + break; + } + } + + int lower = Integer.parseInt(element); + int upper = Integer.parseInt(upperBound); + + if (lower < upper) { + for (int i = lower; i <= upper; i++) { + sequenceList.add(String.valueOf(i)); + } + } else { + for (int i = upper; i <= lower; i++) { + sequenceList.add(String.valueOf(i)); + } + } + + element = ""; + } else if (set.charAt(index) == ',') { + sequenceList.add(element); + element = ""; + } else { + element += set.charAt(index); + } + index++; + } + if (!element.equals("")) { + sequenceList.add(element); + } + return sequenceList; + } + @Override public void moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) @@ -2290,6 +2393,17 @@ public class ImapStore extends Store { return executeSimpleCommand(command, sensitive, null); } +// public void logResponse (ImapList response) { +// for(int i=0;i executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) throws IOException, ImapException, MessagingException { String commandToLog = command; From bc9b7030d7d250c61b7bf8b624c6fa84c0363dd3 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 28 Jun 2011 16:50:48 +0530 Subject: [PATCH 04/80] COPYUID implementation now in place and working, restructured appendMessages, copyMessages and moveMessages globally to return a Map of srcUids -> destUids rather than returning nothing. This is now used to bring local and remote UIDs upto speed without the need for additional requests. --- .../k9/controller/MessagingController.java | 94 ++++++++++++++++--- src/com/fsck/k9/mail/Folder.java | 11 ++- src/com/fsck/k9/mail/store/ImapStore.java | 83 ++++++++-------- src/com/fsck/k9/mail/store/LocalStore.java | 30 ++++-- src/com/fsck/k9/mail/store/Pop3Store.java | 4 +- src/com/fsck/k9/mail/store/WebDavStore.java | 10 +- 6 files changed, 163 insertions(+), 69 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 079b72812..198f4b9a0 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2088,9 +2088,32 @@ public class MessagingController implements Runnable { command.arguments[0] = srcFolder; command.arguments[1] = destFolder; command.arguments[2] = Boolean.toString(isCopy); - System.arraycopy(uids, 0, command.arguments, 3, uids.length); + command.arguments[3] = Boolean.toString(false); + System.arraycopy(uids, 0, command.arguments, 4, uids.length); queuePendingCommand(account, command); } + + private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[], Map uidMap) { + if (uidMap == null || uidMap.isEmpty()) { + queueMoveOrCopy(account, srcFolder, destFolder, isCopy, uids); + } else { + if (account.getErrorFolderName().equals(srcFolder)) { + return; + } + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + + int length = 4 + uidMap.keySet().size() + uidMap.values().size(); + command.arguments = new String[length]; + command.arguments[0] = srcFolder; + command.arguments[1] = destFolder; + command.arguments[2] = Boolean.toString(isCopy); + command.arguments[3] = Boolean.toString(true); + System.arraycopy(uidMap.keySet().toArray(), 0, command.arguments, 4, uidMap.keySet().size()); + System.arraycopy(uidMap.values().toArray(), 0, command.arguments, 4 + uidMap.keySet().size(), uidMap.values().size()); + queuePendingCommand(account, command); + } + } /** * Process a pending trash message command. * @@ -2102,6 +2125,7 @@ public class MessagingController implements Runnable { throws MessagingException { Folder remoteSrcFolder = null; Folder remoteDestFolder = null; + Folder localDestFolder = null; try { String srcFolder = command.arguments[0]; if (account.getErrorFolderName().equals(srcFolder)) { @@ -2109,14 +2133,42 @@ public class MessagingController implements Runnable { } String destFolder = command.arguments[1]; String isCopyS = command.arguments[2]; + String hasNewUidsS = command.arguments[3]; + + boolean hasNewUids = false; + if (hasNewUidsS != null) { + hasNewUids = Boolean.parseBoolean(hasNewUidsS); + } + Store remoteStore = account.getRemoteStore(); remoteSrcFolder = remoteStore.getFolder(srcFolder); + Store localStore = account.getLocalStore(); + localDestFolder = localStore.getFolder(destFolder); List messages = new ArrayList(); - for (int i = 3; i < command.arguments.length; i++) { - String uid = command.arguments[i]; - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { - messages.add(remoteSrcFolder.getMessage(uid)); + + /* + * We split up the localUidMap into two parts while sending the command, here we assemble it back. + */ + Map localUidMap = new HashMap(); + if (hasNewUids) { + int offset = (command.arguments.length - 4) / 2; + + for (int i = 4; i < 4 + offset; i++) { + localUidMap.put(command.arguments[i], command.arguments[i + offset]); + + String uid = command.arguments[i]; + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + messages.add(remoteSrcFolder.getMessage(uid)); + } + } + + } else { + for (int i = 4; i < command.arguments.length; i++) { + String uid = command.arguments[i]; + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + messages.add(remoteSrcFolder.getMessage(uid)); + } } } @@ -2137,6 +2189,8 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder + ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy); + Map remoteUidMap = new HashMap(); + if (!isCopy && destFolder.equals(account.getTrashFolderName())) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy doing special case for deleting message"); @@ -2150,9 +2204,9 @@ public class MessagingController implements Runnable { remoteDestFolder = remoteStore.getFolder(destFolder); if (isCopy) { - remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); + remoteUidMap = remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); } else { - remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); + remoteUidMap = remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); } } if (!isCopy && Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { @@ -2161,12 +2215,27 @@ public class MessagingController implements Runnable { remoteSrcFolder.expunge(); } + + /* + * This next part is used to bring the local UIDs of the local destination folder + * upto speed with the remote UIDs of remote destionation folder. + */ + if (!localUidMap.isEmpty() && !remoteUidMap.isEmpty()) { + Set remoteSrcUids = remoteUidMap.keySet(); + Iterator remoteSrcUidsIterator = remoteSrcUids.iterator(); + + while (remoteSrcUidsIterator.hasNext()) { + String remoteSrcUid = remoteSrcUidsIterator.next(); + String localDestUid = localUidMap.get(remoteSrcUid); + + Message localDestMessage = localDestFolder.getMessage(localDestUid); + localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + } + } } finally { closeFolder(remoteSrcFolder); closeFolder(remoteDestFolder); } - - } private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) { @@ -3248,6 +3317,7 @@ public class MessagingController implements Runnable { private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder, final Message[] inMessages, final String destFolder, final boolean isCopy, MessagingListener listener) { try { + Map uidMap = new HashMap(); Store localStore = account.getLocalStore(); Store remoteStore = account.getRemoteStore(); if (!isCopy && (!remoteStore.isMoveCapable() || !localStore.isMoveCapable())) { @@ -3285,9 +3355,9 @@ public class MessagingController implements Runnable { fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localSrcFolder.fetch(messages, fp, null); - localSrcFolder.copyMessages(messages, localDestFolder); + uidMap = localSrcFolder.copyMessages(messages, localDestFolder); } else { - localSrcFolder.moveMessages(messages, localDestFolder); + uidMap = localSrcFolder.moveMessages(messages, localDestFolder); for (String origUid : origUidMap.keySet()) { for (MessagingListener l : getListeners()) { l.messageUidChanged(account, srcFolder, origUid, origUidMap.get(origUid).getUid()); @@ -3296,7 +3366,7 @@ public class MessagingController implements Runnable { } } - queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); + queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY), uidMap); } processPendingCommands(account); diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java index 0f5b96060..e3dafc3e5 100644 --- a/src/com/fsck/k9/mail/Folder.java +++ b/src/com/fsck/k9/mail/Folder.java @@ -1,6 +1,7 @@ package com.fsck.k9.mail; import java.util.Date; +import java.util.Map; import android.util.Log; import com.fsck.k9.Account; @@ -102,11 +103,15 @@ public abstract class Folder { public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener) throws MessagingException; - public abstract void appendMessages(Message[] messages) throws MessagingException; + public abstract Map appendMessages(Message[] messages) throws MessagingException; - public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {} + public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException { + return null; + } - public void moveMessages(Message[] msgs, Folder folder) throws MessagingException {} + public Map moveMessages(Message[] msgs, Folder folder) throws MessagingException { + return null; + } public void delete(Message[] msgs, String trashFolderName) throws MessagingException { for (Message message : msgs) { diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 8824e0988..b4da67248 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -29,7 +29,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -40,9 +39,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.SortedSet; import java.util.StringTokenizer; -import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -806,32 +803,19 @@ public class ImapStore extends Store { } @Override - public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { if (!(folder instanceof ImapFolder)) { throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder"); } if (messages.length == 0) - return; + return null; ImapFolder iFolder = (ImapFolder)folder; checkOpen(); - SortedSet messageSet = new TreeSet(new Comparator() { - public int compare(Message m1, Message m2) { - int uid1 = Integer.parseInt(m1.getUid()), uid2 = Integer.parseInt(m2.getUid()); - if (uid1 < uid2) { - return -1; - } else if (uid1 == uid2) { - return 0; - } else { - return 1; - } - } - }); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { - messageSet.add(messages[i]); // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); @@ -875,31 +859,36 @@ public class ImapStore extends Store { Object responseList = response.get(1); + Map uidMap = null; + if (responseList instanceof ImapList) { final ImapList copyList = (ImapList) responseList; if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { - List oldUids = parseSequenceSet(copyList.getString(2)); - List newUids = parseSequenceSet(copyList.getString(3)); - if (oldUids.size() == newUids.size()) { - Iterator messageIterator = messageSet.iterator(); - for (int i = 0; i < messages.length && messageIterator.hasNext(); i++) { - Message nextMessage = messageIterator.next(); - if (oldUids.get(i).equals(nextMessage.getUid())) { - /* - * Here, we need to *create* new messages in the localstore, same as the older messages, the only changes are that old UIDs need to be swapped with new UIDs, - * and old folder swapped with new folder. - */ -// nextMessage.setUid(newUids.get(i)); - } + List srcUids = parseSequenceSet(copyList.getString(2)); + List destUids = parseSequenceSet(copyList.getString(3)); + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { + uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); } } } } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } } + /** + * Can be used to parse sequence sets or UID sets appearing is responses such as COPYUID. + * e.g. [COPYUID 38505 304,319:320 3956:3958] + * + * @param set + * @return List sequenceSet + */ private List parseSequenceSet(String set) { int index = 0; List sequenceList = new ArrayList(); @@ -947,11 +936,12 @@ public class ImapStore extends Store { } @Override - public void moveMessages(Message[] messages, Folder folder) throws MessagingException { + public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) - return; - copyMessages(messages, folder); + return null; + Map uidMap = copyMessages(messages, folder); setFlags(messages, new Flag[] { Flag.DELETED }, true); + return uidMap; } @Override @@ -1698,9 +1688,10 @@ public class ImapStore extends Store { * new server UID. */ @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { checkOpen(); try { + Map uidMap = null; for (Message message : messages) { mConnection.sendCommand( String.format("APPEND %s (%s) {%d}", @@ -1734,9 +1725,18 @@ public class ImapStore extends Store { if (responseList instanceof ImapList) { final ImapList appendList = (ImapList) responseList; if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { - String serverUid = appendList.getString(2); - if (!TextUtils.isEmpty(serverUid)) { - message.setUid(serverUid); + String newUid = appendList.getString(2); + + /* + * We need uidMap to be null initially to maintain consistency with the behavior of other similar methods (copyMessages, moveMessages) which + * return null if new UIDs are not available. Therefore, we initialize uidMap over here. + */ + if (uidMap == null) { + uidMap = new HashMap(); + } + uidMap.put(message.getUid(), newUid); + if (!TextUtils.isEmpty(newUid)) { + message.setUid(newUid); continue; } } @@ -1751,11 +1751,14 @@ public class ImapStore extends Store { Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); if (newUid != null) { + if (uidMap == null) { + uidMap = new HashMap(); + } + uidMap.put(message.getUid(), newUid); message.setUid(newUid); } - - } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index fba282164..0c3647034 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1885,21 +1885,23 @@ public class LocalStore extends Store implements Serializable { } @Override - public void copyMessages(Message[] msgs, Folder folder) throws MessagingException { + public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException { if (!(folder instanceof LocalFolder)) { throw new MessagingException("copyMessages called with incorrect Folder"); } - ((LocalFolder) folder).appendMessages(msgs, true); + return ((LocalFolder) folder).appendMessages(msgs, true); } @Override - public void moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException { + public Map moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException { if (!(destFolder instanceof LocalFolder)) { throw new MessagingException("moveMessages called with non-LocalFolder"); } final LocalFolder lDestFolder = (LocalFolder)destFolder; + final Map uidMap = new HashMap(); + try { database.execute(false, new DbCallback() { @Override @@ -1936,7 +1938,7 @@ public class LocalStore extends Store implements Serializable { LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this); placeHolder.setFlagInternal(Flag.DELETED, true); placeHolder.setFlagInternal(Flag.SEEN, true); - appendMessages(new Message[] { placeHolder }); + uidMap.putAll(appendMessages(new Message[] { placeHolder })); } } catch (MessagingException e) { throw new WrappedException(e); @@ -1944,6 +1946,7 @@ public class LocalStore extends Store implements Serializable { return null; } }); + return uidMap; } catch (WrappedException e) { throw(MessagingException) e.getCause(); } @@ -1989,8 +1992,8 @@ public class LocalStore extends Store implements Serializable { * message, retrieve the appropriate local message instance first (if it already exists). */ @Override - public void appendMessages(Message[] messages) throws MessagingException { - appendMessages(messages, false); + public Map appendMessages(Message[] messages) throws MessagingException { + return appendMessages(messages, false); } public void destroyMessages(final Message[] messages) throws MessagingException { @@ -2026,10 +2029,12 @@ public class LocalStore extends Store implements Serializable { * message, retrieve the appropriate local message instance first (if it already exists). * @param messages * @param copy + * @return Map uidMap of srcUids -> destUids */ - private void appendMessages(final Message[] messages, final boolean copy) throws MessagingException { + private Map appendMessages(final Message[] messages, final boolean copy) throws MessagingException { open(OpenMode.READ_WRITE); try { + final Map uidMap = new HashMap(); database.execute(true, new DbCallback() { @Override public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { @@ -2040,11 +2045,15 @@ public class LocalStore extends Store implements Serializable { } String uid = message.getUid(); - if (uid == null || copy) { + if (uid == null && !copy) { uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - if (!copy) { - message.setUid(uid); + message.setUid(uid); + } else if (copy) { + String temp = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + if (uid != null) { + uidMap.put(uid, temp); } + uid = temp; } else { Message oldMessage = getMessage(uid); if (oldMessage != null && !oldMessage.isSet(Flag.SEEN)) { @@ -2150,6 +2159,7 @@ public class LocalStore extends Store implements Serializable { return null; } }); + return uidMap; } catch (WrappedException e) { throw(MessagingException) e.getCause(); } diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index a307c8484..e0c57591e 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -24,6 +24,7 @@ import java.util.LinkedList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; public class Pop3Store extends Store { public static final int CONNECTION_SECURITY_NONE = 0; @@ -736,7 +737,8 @@ public class Pop3Store extends Store { } @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { + return null; } @Override diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 81a498f25..7720b9c18 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Stack; import java.util.zip.GZIPInputStream; @@ -1161,13 +1162,15 @@ public class WebDavStore extends Store { } @Override - public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { moveOrCopyMessages(messages, folder.getName(), false); + return null; } @Override - public void moveMessages(Message[] messages, Folder folder) throws MessagingException { + public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { moveOrCopyMessages(messages, folder.getName(), true); + return null; } @Override @@ -1728,8 +1731,9 @@ public class WebDavStore extends Store { } @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { appendWebDavMessages(messages); + return null; } public Message[] appendWebDavMessages(Message[] messages) throws MessagingException { From 3321ebdc33f3e51c145d20cc627cafe09021ad81 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Sat, 2 Jul 2011 11:59:00 +0530 Subject: [PATCH 05/80] COPYUID changes were being updated only in memory, but were not being written to db. --- src/com/fsck/k9/controller/MessagingController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 198f4b9a0..c971eebce 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2125,7 +2125,7 @@ public class MessagingController implements Runnable { throws MessagingException { Folder remoteSrcFolder = null; Folder remoteDestFolder = null; - Folder localDestFolder = null; + LocalFolder localDestFolder = null; try { String srcFolder = command.arguments[0]; if (account.getErrorFolderName().equals(srcFolder)) { @@ -2144,7 +2144,7 @@ public class MessagingController implements Runnable { remoteSrcFolder = remoteStore.getFolder(srcFolder); Store localStore = account.getLocalStore(); - localDestFolder = localStore.getFolder(destFolder); + localDestFolder = (LocalFolder) localStore.getFolder(destFolder); List messages = new ArrayList(); /* @@ -2230,6 +2230,7 @@ public class MessagingController implements Runnable { Message localDestMessage = localDestFolder.getMessage(localDestUid); localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + localDestFolder.changeUid((LocalMessage)localDestMessage); } } } finally { From da9a5e6c17ce89d2fc54cebf35c5397fd609e93d Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Mon, 25 Jul 2011 06:50:26 +0530 Subject: [PATCH 06/80] Made some cosmetic changes for clarity, added debug messages for erroneous conditions. Fixed potential NPE in ImapFolder.parseSequenceSet(). --- src/com/fsck/k9/mail/store/ImapStore.java | 26 +++++++++++++++++----- src/com/fsck/k9/mail/store/LocalStore.java | 6 ++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index b4da67248..4e743e70a 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -866,15 +866,26 @@ public class ImapStore extends Store { if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { List srcUids = parseSequenceSet(copyList.getString(2)); List destUids = parseSequenceSet(copyList.getString(3)); - if (srcUids.size() == destUids.size()) { - Iterator srcUidsIterator = srcUids.iterator(); - Iterator destUidsIterator = destUids.iterator(); - uidMap = new HashMap(); - while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { - uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + if (srcUids != null && destUids != null) { + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { + uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } return uidMap; } catch (IOException ioe) { @@ -890,6 +901,9 @@ public class ImapStore extends Store { * @return List sequenceSet */ private List parseSequenceSet(String set) { + if (set == null) { + return null; + } int index = 0; List sequenceList = new ArrayList(); String element = ""; diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 0c3647034..7a5a6b437 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2049,11 +2049,11 @@ public class LocalStore extends Store implements Serializable { uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); message.setUid(uid); } else if (copy) { - String temp = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + String randomLocalUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); if (uid != null) { - uidMap.put(uid, temp); + uidMap.put(uid, randomLocalUid); } - uid = temp; + uid = randomLocalUid; } else { Message oldMessage = getMessage(uid); if (oldMessage != null && !oldMessage.isSet(Flag.SEEN)) { From cf390700487daf0f6611415d4531f9b013195b46 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Mon, 25 Jul 2011 18:56:21 +0530 Subject: [PATCH 07/80] Whitespaces :/ --- src/com/fsck/k9/mail/store/ImapStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 4e743e70a..d9f8b4548 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -875,16 +875,16 @@ public class ImapStore extends Store { uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } return uidMap; From 4b0d3ccf21caf250806d5b15565d90b0e242b495 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Thu, 17 Nov 2011 02:46:01 +0530 Subject: [PATCH 08/80] Removed an unnecessary portion of code that attempted to handle command continuation requests after a UID COPY command. Also removed some extraneous test code that mysteriously creeped into the repo. --- src/com/fsck/k9/mail/store/ImapStore.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index d9f8b4548..a0efebeee 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -839,12 +839,6 @@ public class ImapStore extends Store { do { response = mConnection.readResponse(); handleUntaggedResponse(response); - if (response.mCommandContinuationRequested) { - EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(mConnection.mOut); - eolOut.write('\r'); - eolOut.write('\n'); - eolOut.flush(); - } while (response.more()); } while (response.mTag == null); @@ -2410,17 +2404,6 @@ public class ImapStore extends Store { return executeSimpleCommand(command, sensitive, null); } -// public void logResponse (ImapList response) { -// for(int i=0;i executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) throws IOException, ImapException, MessagingException { String commandToLog = command; From 396005974a269d03f66785f53aa91adbd4c2539d Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 21:33:53 +0100 Subject: [PATCH 09/80] Cleaned up ImapStore.ImapFolder.appendMessages() --- src/com/fsck/k9/mail/store/ImapStore.java | 85 +++++++++++++---------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index abd8cd2e2..461339d41 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -54,7 +54,6 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.PowerManager; -import android.text.TextUtils; import android.util.Log; import com.beetstra.jutf7.CharsetProvider; @@ -62,6 +61,7 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; import com.fsck.k9.controller.MessageRetrievalListener; +import com.fsck.k9.helper.StringUtils; import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.power.TracingPowerManager; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; @@ -1099,7 +1099,6 @@ public class ImapStore extends Store { do { response = mConnection.readResponse(); handleUntaggedResponse(response); - while (response.more()); } while (response.mTag == null); /* @@ -1953,21 +1952,30 @@ public class ImapStore extends Store { } /** - * Appends the given messages to the selected folder. This implementation also determines - * the new UID of the given message on the IMAP server and sets the Message's UID to the - * new server UID. + * Appends the given messages to the selected folder. + * + *

+ * This implementation also determines the new UIDs of the given messages on the IMAP + * server and changes the messages' UIDs to the new server UIDs. + *

+ * + * @param messages + * The messages to append to the folder. + * + * @return The mapping of original message UIDs to the new server UIDs. */ @Override public Map appendMessages(Message[] messages) throws MessagingException { checkOpen(); try { - Map uidMap = null; + Map uidMap = new HashMap(); for (Message message : messages) { mConnection.sendCommand( String.format("APPEND %s (%s) {%d}", encodeString(encodeFolderName(getPrefixedName())), combineFlags(message.getFlags()), message.calculateSize()), false); + ImapResponse response; do { response = mConnection.readResponse(); @@ -1981,53 +1989,54 @@ public class ImapStore extends Store { } } while (response.mTag == null); - /* - * If the server supports UIDPLUS, then along with the APPEND response it will return an APPENDUID response code. - * e.g. 11 OK [APPENDUID 2 238268] APPEND completed - * - * We can use the UID included in this response to update our records. - * - * Code imported from AOSP Email as of git rev a5c3744a247e432acf9f571a9dfb55321c4baa1a - */ - Object responseList = response.get(1); + if (response.size() > 1) { + /* + * If the server supports UIDPLUS, then along with the APPEND response it + * will return an APPENDUID response code, e.g. + * + * 11 OK [APPENDUID 2 238268] APPEND completed + * + * We can use the UID included in this response to update our records. + */ + Object responseList = response.get(1); - if (responseList instanceof ImapList) { - final ImapList appendList = (ImapList) responseList; - if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { - String newUid = appendList.getString(2); + if (responseList instanceof ImapList) { + ImapList appendList = (ImapList) responseList; + if (appendList.size() >= 3 && + appendList.getString(0).equals("APPENDUID")) { - /* - * We need uidMap to be null initially to maintain consistency with the behavior of other similar methods (copyMessages, moveMessages) which - * return null if new UIDs are not available. Therefore, we initialize uidMap over here. - */ - if (uidMap == null) { - uidMap = new HashMap(); - } - uidMap.put(message.getUid(), newUid); - if (!TextUtils.isEmpty(newUid)) { - message.setUid(newUid); - continue; + String newUid = appendList.getString(2); + + if (!StringUtils.isNullOrEmpty(newUid)) { + message.setUid(newUid); + uidMap.put(message.getUid(), newUid); + continue; + } } } } - /* - * This part is executed in case the server does not support UIDPLUS or does not implement the APPENDUID response code. + * This part is executed in case the server does not support UIDPLUS or does + * not implement the APPENDUID response code. */ String newUid = getUidFromMessageId(message); - if (K9.DEBUG) + if (K9.DEBUG) { Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); + } - if (newUid != null) { - if (uidMap == null) { - uidMap = new HashMap(); - } + if (!StringUtils.isNullOrEmpty(newUid)) { uidMap.put(message.getUid(), newUid); message.setUid(newUid); } } - return uidMap; + + /* + * We need uidMap to be null if new UIDs are not available to maintain consistency + * with the behavior of other similar methods (copyMessages, moveMessages) which + * return null. + */ + return (uidMap.size() == 0) ? null : uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } From 3e4e6c72fd5180db967ddeaca5febe47954b7121 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:12:44 +0100 Subject: [PATCH 10/80] Cleaned up ImapStore.ImapFolder.copyMessages() --- src/com/fsck/k9/mail/store/ImapStore.java | 108 ++++++++++++++-------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 461339d41..32b07a6bd 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -1062,36 +1062,54 @@ public class ImapStore extends Store { } } + /** + * Copies the given messages to the specified folder. + * + *

+ * Note: + * Only the UIDs of the given {@link Message} instances are used. It is assumed that all + * UIDs represent valid messages in this folder. + *

+ * + * @param messages + * The messages to copy to the specfied folder. + * @param folder + * The name of the target folder. + * + * @return The mapping of original message UIDs to the new server UIDs. + */ @Override - public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) + throws MessagingException { if (!(folder instanceof ImapFolder)) { throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder"); } - if (messages.length == 0) + if (messages.length == 0) { return null; + } ImapFolder iFolder = (ImapFolder)folder; checkOpen(); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { - - // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); } + try { String remoteDestName = encodeString(encodeFolderName(iFolder.getPrefixedName())); if (!exists(remoteDestName)) { - /* - * If the remote trash folder doesn't exist we try to create it. - */ - if (K9.DEBUG) - Log.i(K9.LOG_TAG, "IMAPMessage.copyMessages: attempting to create remote '" + remoteDestName + "' folder for " + getLogId()); + // If the remote trash folder doesn't exist we try to create it. + if (K9.DEBUG) { + Log.i(K9.LOG_TAG, "Attempting to create remote folder '" + remoteDestName + + "' for " + getLogId()); + } iFolder.create(FolderType.HOLDS_MESSAGES); } + //TODO: Split this into multiple commands if the command exceeds a certain length. mConnection.sendCommand(String.format("UID COPY %s %s", Utility.combine(uids, ','), remoteDestName), false); @@ -1101,45 +1119,55 @@ public class ImapStore extends Store { handleUntaggedResponse(response); } while (response.mTag == null); - /* - * If the server supports UIDPLUS, then along with the COPY response it will return an COPYUID response code. - * e.g. 24 OK [COPYUID 38505 304,319:320 3956:3958] Success - * - * COPYUID is followed by UIDVALIDITY, set of UIDs of copied messages from the source folder and set of corresponding UIDs assigned to them in the destination folder. - * - * We can use the new UIDs included in this response to update our records. - */ - - Object responseList = response.get(1); - Map uidMap = null; + if (response.size() > 1) { + /* + * If the server supports UIDPLUS, then along with the COPY response it will + * return an COPYUID response code, e.g. + * + * 24 OK [COPYUID 38505 304,319:320 3956:3958] Success + * + * COPYUID is followed by UIDVALIDITY, the set of UIDs of copied messages from + * the source folder and the set of corresponding UIDs assigned to them in the + * destination folder. + * + * We can use the new UIDs included in this response to update our records. + */ + Object responseList = response.get(1); - if (responseList instanceof ImapList) { - final ImapList copyList = (ImapList) responseList; - if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { - List srcUids = parseSequenceSet(copyList.getString(2)); - List destUids = parseSequenceSet(copyList.getString(3)); - if (srcUids != null && destUids != null) { - if (srcUids.size() == destUids.size()) { - Iterator srcUidsIterator = srcUids.iterator(); - Iterator destUidsIterator = destUids.iterator(); - uidMap = new HashMap(); - while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { - uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + if (responseList instanceof ImapList) { + final ImapList copyList = (ImapList) responseList; + if (copyList.size() >= 4 && copyList.getString(0).equals("COPYUID")) { + List srcUids = parseSequenceSet(copyList.getString(2)); + List destUids = parseSequenceSet(copyList.getString(3)); + + if (srcUids != null && destUids != null) { + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && + destUidsIterator.hasNext()) { + String srcUid = srcUidsIterator.next(); + String destUid = destUidsIterator.next(); + uidMap.put(srcUid, destUid); + } + } else { + if (K9.DEBUG) { + Log.v(K9.LOG_TAG, "Parse error: size of source UIDs " + + "list is not the same as size of destination " + + "UIDs list."); + } } } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); + if (K9.DEBUG) { + Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); + } } - } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } - } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); From 89e0ed3c03a6fbab38ffa4c7b45c4fdd4a63de1b Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:34:50 +0100 Subject: [PATCH 11/80] Notify listeners of UID changes after remote copy/move --- src/com/fsck/k9/controller/MessagingController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 61ae0f1b4..6673df75f 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2227,10 +2227,14 @@ public class MessagingController implements Runnable { while (remoteSrcUidsIterator.hasNext()) { String remoteSrcUid = remoteSrcUidsIterator.next(); String localDestUid = localUidMap.get(remoteSrcUid); + String newUid = remoteUidMap.get(remoteSrcUid); Message localDestMessage = localDestFolder.getMessage(localDestUid); - localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + localDestMessage.setUid(newUid); localDestFolder.changeUid((LocalMessage)localDestMessage); + for (MessagingListener l : getListeners()) { + l.messageUidChanged(account, destFolder, localDestUid, newUid); + } } } } finally { From 4ed77c7a29a508240e926854505953391bb17dfb Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:36:57 +0100 Subject: [PATCH 12/80] Avoid NullPointerException --- src/com/fsck/k9/controller/MessagingController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 6673df75f..06fb59370 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2189,7 +2189,7 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder + ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy); - Map remoteUidMap = new HashMap(); + Map remoteUidMap = null; if (!isCopy && destFolder.equals(account.getTrashFolderName())) { if (K9.DEBUG) @@ -2220,7 +2220,7 @@ public class MessagingController implements Runnable { * This next part is used to bring the local UIDs of the local destination folder * upto speed with the remote UIDs of remote destionation folder. */ - if (!localUidMap.isEmpty() && !remoteUidMap.isEmpty()) { + if (!localUidMap.isEmpty() && remoteUidMap != null && !remoteUidMap.isEmpty()) { Set remoteSrcUids = remoteUidMap.keySet(); Iterator remoteSrcUidsIterator = remoteSrcUids.iterator(); From 023c60513eeb308f280c836b3334c0959d5de4b3 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 23:37:44 +0100 Subject: [PATCH 13/80] Fixed UID mapping return value of LocalStore.moveMessages() --- src/com/fsck/k9/mail/store/LocalStore.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index ca8980ab0..c36350dfa 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1963,7 +1963,10 @@ public class LocalStore extends Store implements Serializable { Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID " + message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName()); - message.setUid(K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString()); + String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + message.setUid(newUid); + + uidMap.put(oldUID, newUid); db.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] { lDestFolder.getId(), @@ -1971,10 +1974,15 @@ public class LocalStore extends Store implements Serializable { lMessage.getId() }); + /* + * Add a placeholder message so we won't download the original + * message again if we synchronize before the remote move is + * complete. + */ LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this); placeHolder.setFlagInternal(Flag.DELETED, true); placeHolder.setFlagInternal(Flag.SEEN, true); - uidMap.putAll(appendMessages(new Message[] { placeHolder })); + appendMessages(new Message[] { placeHolder }); } } catch (MessagingException e) { throw new WrappedException(e); From c3885bc475f95dc655d3adbc706749c3d78d0add Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 23:52:44 +0100 Subject: [PATCH 14/80] Cleaned up LocalFolder.appendMessages() a bit --- src/com/fsck/k9/mail/store/LocalStore.java | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index c36350dfa..bf797ebfe 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2090,16 +2090,27 @@ public class LocalStore extends Store implements Serializable { long oldMessageId = -1; String uid = message.getUid(); - if (uid == null && !copy) { - uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - message.setUid(uid); - } else if (copy) { - String randomLocalUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - if (uid != null) { + if (uid == null || copy) { + /* + * Create a new message in the database + */ + String randomLocalUid = K9.LOCAL_UID_PREFIX + + UUID.randomUUID().toString(); + + if (copy) { + // Save mapping: source UID -> target UID uidMap.put(uid, randomLocalUid); + } else { + // Modify the Message instance to reference the new UID + message.setUid(randomLocalUid); } + + // The message will be saved with the newly generated UID uid = randomLocalUid; } else { + /* + * Replace an existing message in the database + */ LocalMessage oldMessage = (LocalMessage) getMessage(uid); if (oldMessage != null) { From f7299a69d44efbc36d864a0096d1f741d2a797d6 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 00:43:35 +0100 Subject: [PATCH 15/80] Keep track of UIDs when moving messages to the trash folder --- src/com/fsck/k9/controller/MessagingController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 06fb59370..cc09d8e31 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -3515,6 +3515,7 @@ public class MessagingController implements Runnable { } Store localStore = account.getLocalStore(); localFolder = localStore.getFolder(folder); + Map uidMap = null; if (folder.equals(account.getTrashFolderName()) || K9.FOLDER_NONE.equals(account.getTrashFolderName())) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Deleting messages in trash folder or trash set to -None-, not copying"); @@ -3529,7 +3530,7 @@ public class MessagingController implements Runnable { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Deleting messages in normal folder, moving"); - localFolder.moveMessages(messages, localTrashFolder); + uidMap = localFolder.moveMessages(messages, localTrashFolder); } } @@ -3562,7 +3563,7 @@ public class MessagingController implements Runnable { if (folder.equals(account.getTrashFolderName())) { queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); } else { - queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids); + queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids, uidMap); } processPendingCommands(account); } else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) { From c1ed0c78a9c8c08178565ccfa781cb9762884ad0 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 01:59:04 +0100 Subject: [PATCH 16/80] Introduced new pending command to retain upgradability --- .../k9/controller/MessagingController.java | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index cc09d8e31..008b9afe0 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -117,6 +117,7 @@ public class MessagingController implements Runnable { private static final String PENDING_COMMAND_MOVE_OR_COPY = "com.fsck.k9.MessagingController.moveOrCopy"; private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK = "com.fsck.k9.MessagingController.moveOrCopyBulk"; + private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW = "com.fsck.k9.MessagingController.moveOrCopyBulkNew"; private static final String PENDING_COMMAND_EMPTY_TRASH = "com.fsck.k9.MessagingController.emptyTrash"; private static final String PENDING_COMMAND_SET_FLAG_BULK = "com.fsck.k9.MessagingController.setFlagBulk"; private static final String PENDING_COMMAND_SET_FLAG = "com.fsck.k9.MessagingController.setFlag"; @@ -1902,6 +1903,8 @@ public class MessagingController implements Runnable { } else if (PENDING_COMMAND_MARK_ALL_AS_READ.equals(command.command)) { processPendingMarkAllAsRead(command, account); } else if (PENDING_COMMAND_MOVE_OR_COPY_BULK.equals(command.command)) { + processPendingMoveOrCopyOld2(command, account); + } else if (PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW.equals(command.command)) { processPendingMoveOrCopy(command, account); } else if (PENDING_COMMAND_MOVE_OR_COPY.equals(command.command)) { processPendingMoveOrCopyOld(command, account); @@ -2081,7 +2084,7 @@ public class MessagingController implements Runnable { return; } PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; int length = 3 + uids.length; command.arguments = new String[length]; @@ -2101,7 +2104,7 @@ public class MessagingController implements Runnable { return; } PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; int length = 4 + uidMap.keySet().size() + uidMap.values().size(); command.arguments = new String[length]; @@ -2114,6 +2117,39 @@ public class MessagingController implements Runnable { queuePendingCommand(account, command); } } + + /** + * Convert pending command to new format and call + * {@link #processPendingMoveOrCopy(PendingCommand, Account)}. + * + *

+ * TODO: This method is obsolete and is only for transition from K-9 4.0 to K-9 4.2 + * Eventually, it should be removed. + *

+ * + * @param command + * Pending move/copy command in old format. + * @param account + * The account the pending command belongs to. + * + * @throws MessagingException + * In case of an error. + */ + private void processPendingMoveOrCopyOld2(PendingCommand command, Account account) + throws MessagingException { + PendingCommand newCommand = new PendingCommand(); + int len = command.arguments.length; + newCommand.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; + newCommand.arguments = new String[len + 1]; + newCommand.arguments[0] = command.arguments[0]; + newCommand.arguments[1] = command.arguments[1]; + newCommand.arguments[2] = command.arguments[2]; + newCommand.arguments[3] = Boolean.toString(false); + System.arraycopy(command.arguments, 3, newCommand.arguments, 4, len - 3); + + processPendingMoveOrCopy(newCommand, account); + } + /** * Process a pending trash message command. * From deb01bcd16184eeaeac63b9f22c5b91ad6600682 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 14:59:50 +0100 Subject: [PATCH 17/80] Imported ImapUtility.java from AOSP Email Modified it to fit our needs. --- .../fsck/k9/mail/store/imap/ImapUtility.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/com/fsck/k9/mail/store/imap/ImapUtility.java diff --git a/src/com/fsck/k9/mail/store/imap/ImapUtility.java b/src/com/fsck/k9/mail/store/imap/ImapUtility.java new file mode 100644 index 000000000..d0ac2b31a --- /dev/null +++ b/src/com/fsck/k9/mail/store/imap/ImapUtility.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 The K-9 Dog Walkers + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fsck.k9.mail.store.imap; + +import android.util.Log; + +import com.fsck.k9.K9; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for use with IMAP. + */ +public class ImapUtility { + /** + * Gets all of the values in a sequence set per RFC 3501. + * + *

+ * Any ranges are expanded into a list of individual numbers. + *

+ * + *
+     * sequence-number = nz-number / "*"
+     * sequence-range  = sequence-number ":" sequence-number
+     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
+     * 
+ * + * @param set + * The sequence set string as received by the server. + * + * @return The list of IDs as strings in this sequence set. If the set is invalid, an empty + * list is returned. + */ + public static List getImapSequenceValues(String set) { + ArrayList list = new ArrayList(); + if (set != null) { + String[] setItems = set.split(","); + for (String item : setItems) { + if (item.indexOf(':') == -1) { + // simple item + try { + Integer.parseInt(item); // Don't need the value; just ensure it's valid + list.add(item); + } catch (NumberFormatException e) { + Log.d(K9.LOG_TAG, "Invalid UID value", e); + } + } else { + // range + list.addAll(getImapRangeValues(item)); + } + } + } + + return list; + } + + /** + * Expand the given number range into a list of individual numbers. + * + *
+     * sequence-number = nz-number / "*"
+     * sequence-range  = sequence-number ":" sequence-number
+     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
+     * 
+ * + * @param range + * The range string as received by the server. + * + * @return The list of IDs as strings in this range. If the range is not valid, an empty list + * is returned. + */ + public static List getImapRangeValues(String range) { + ArrayList list = new ArrayList(); + try { + if (range != null) { + int colonPos = range.indexOf(':'); + if (colonPos > 0) { + int first = Integer.parseInt(range.substring(0, colonPos)); + int second = Integer.parseInt(range.substring(colonPos + 1)); + if (first < second) { + for (int i = first; i <= second; i++) { + list.add(Integer.toString(i)); + } + } else { + for (int i = first; i >= second; i--) { + list.add(Integer.toString(i)); + } + } + } + } + } catch (NumberFormatException e) { + Log.d(K9.LOG_TAG, "Invalid range value", e); + } + + return list; + } +} From 5083b8f1e866a6783f5afe074d13398d493513db Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 15:01:30 +0100 Subject: [PATCH 18/80] Use newly imported AOSP code instead of ImapStore.parseSequenceSet() --- src/com/fsck/k9/mail/store/ImapStore.java | 63 ++--------------------- 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 32b07a6bd..b0ecd2adc 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -90,6 +90,7 @@ import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.ImapResponseParser.ImapList; import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse; +import com.fsck.k9.mail.store.imap.ImapUtility; import com.fsck.k9.mail.transport.imap.ImapSettings; import com.jcraft.jzlib.JZlib; import com.jcraft.jzlib.ZOutputStream; @@ -1138,8 +1139,10 @@ public class ImapStore extends Store { if (responseList instanceof ImapList) { final ImapList copyList = (ImapList) responseList; if (copyList.size() >= 4 && copyList.getString(0).equals("COPYUID")) { - List srcUids = parseSequenceSet(copyList.getString(2)); - List destUids = parseSequenceSet(copyList.getString(3)); + List srcUids = ImapUtility.getImapSequenceValues( + copyList.getString(2)); + List destUids = ImapUtility.getImapSequenceValues( + copyList.getString(3)); if (srcUids != null && destUids != null) { if (srcUids.size() == destUids.size()) { @@ -1174,62 +1177,6 @@ public class ImapStore extends Store { } } - /** - * Can be used to parse sequence sets or UID sets appearing is responses such as COPYUID. - * e.g. [COPYUID 38505 304,319:320 3956:3958] - * - * @param set - * @return List sequenceSet - */ - private List parseSequenceSet(String set) { - if (set == null) { - return null; - } - int index = 0; - List sequenceList = new ArrayList(); - String element = ""; - - while (index < set.length()) { - if (set.charAt(index) == ':') { - String upperBound = ""; - index++; - while (index < set.length()) { - if (!(set.charAt(index) == ',')) { - upperBound += set.charAt(index); - index++; - } else { - break; - } - } - - int lower = Integer.parseInt(element); - int upper = Integer.parseInt(upperBound); - - if (lower < upper) { - for (int i = lower; i <= upper; i++) { - sequenceList.add(String.valueOf(i)); - } - } else { - for (int i = upper; i <= lower; i++) { - sequenceList.add(String.valueOf(i)); - } - } - - element = ""; - } else if (set.charAt(index) == ',') { - sequenceList.add(element); - element = ""; - } else { - element += set.charAt(index); - } - index++; - } - if (!element.equals("")) { - sequenceList.add(element); - } - return sequenceList; - } - @Override public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) From 33029a1f17149a218d6bb03ce8a4629eb5c4e0b7 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 15:34:10 +0100 Subject: [PATCH 19/80] Imported unit tests for ImapUtility from AOSP Email --- .../k9/mail/store/imap/ImapUtilityTest.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java diff --git a/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java b/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java new file mode 100644 index 000000000..496d6922e --- /dev/null +++ b/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 The K-9 Dog Walkers + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fsck.k9.mail.store.imap; + +import java.util.List; +import android.test.MoreAsserts; +import junit.framework.TestCase; + +public class ImapUtilityTest extends TestCase { + /** + * Test getting elements of an IMAP sequence set. + */ + public void testGetImapSequenceValues() { + String[] expected; + List actual; + + // Test valid sets + expected = new String[] {"1"}; + actual = ImapUtility.getImapSequenceValues("1"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"1", "3", "2"}; + actual = ImapUtility.getImapSequenceValues("1,3,2"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"4", "5", "6"}; + actual = ImapUtility.getImapSequenceValues("4:6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"9", "8", "7"}; + actual = ImapUtility.getImapSequenceValues("9:7"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"1", "2", "3", "4", "9", "8", "7"}; + actual = ImapUtility.getImapSequenceValues("1,2:4,9:7"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test partially invalid sets + expected = new String[] { "1", "5" }; + actual = ImapUtility.getImapSequenceValues("1,x,5"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] { "1", "2", "3" }; + actual = ImapUtility.getImapSequenceValues("a:d,1:3"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test invalid sets + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues(""); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues(null); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues("a"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues("1:x"); + MoreAsserts.assertEquals(expected, actual.toArray()); + } + + /** + * Test getting elements of an IMAP range. + */ + public void testGetImapRangeValues() { + String[] expected; + List actual; + + // Test valid ranges + expected = new String[] {"1", "2", "3"}; + actual = ImapUtility.getImapRangeValues("1:3"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"16", "15", "14"}; + actual = ImapUtility.getImapRangeValues("16:14"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test in-valid ranges + expected = new String[0]; + actual = ImapUtility.getImapRangeValues(""); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues(null); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("a"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:3,6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:x"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:*"); + MoreAsserts.assertEquals(expected, actual.toArray()); + } +} From f6ebe4d4e08ec281c0931c0b46bdc582b308bea8 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 29 Feb 2012 03:03:20 +0100 Subject: [PATCH 20/80] Deduplicated code to create SearchAccounts for special accounts --- src/com/fsck/k9/SearchAccount.java | 33 +++++++++++++++++++ src/com/fsck/k9/activity/AccountList.java | 13 +++----- src/com/fsck/k9/activity/Accounts.java | 11 ++----- src/com/fsck/k9/provider/MessageProvider.java | 4 +-- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/com/fsck/k9/SearchAccount.java b/src/com/fsck/k9/SearchAccount.java index b990e8d09..bf2f25570 100644 --- a/src/com/fsck/k9/SearchAccount.java +++ b/src/com/fsck/k9/SearchAccount.java @@ -12,6 +12,39 @@ import com.fsck.k9.mail.Flag; * is defined by {@link com.fsck.k9.activity.SearchModifier}. */ public class SearchAccount implements BaseAccount, SearchSpecification, Serializable { + /** + * Create a {@code SearchAccount} instance for the Unified Inbox. + * + * @param context + * A {@link Context} instance that will be used to get localized strings and will be + * passed on to the {@code SearchAccount} instance. + * + * @return The {@link SearchAccount} instance for the Unified Inbox. + */ + public static SearchAccount createUnifiedInboxAccount(Context context) { + SearchAccount unifiedInbox = new SearchAccount(context, true, null, null); + unifiedInbox.setDescription(context.getString(R.string.integrated_inbox_title)); + unifiedInbox.setEmail(context.getString(R.string.integrated_inbox_detail)); + return unifiedInbox; + } + + /** + * Create a {@code SearchAccount} instance for the special account "All messages". + * + * @param context + * A {@link Context} instance that will be used to get localized strings and will be + * passed on to the {@code SearchAccount} instance. + * + * @return The {@link SearchAccount} instance for the Unified Inbox. + */ + public static SearchAccount createAllMessagesAccount(Context context) { + SearchAccount allMessages = new SearchAccount(context, false, null, null); + allMessages.setDescription(context.getString(R.string.search_all_messages_title)); + allMessages.setEmail(context.getString(R.string.search_all_messages_detail)); + return allMessages; + } + + private static final long serialVersionUID = -4388420303235543976L; private Flag[] mRequiredFlags = null; private Flag[] mForbiddenFlags = null; diff --git a/src/com/fsck/k9/activity/AccountList.java b/src/com/fsck/k9/activity/AccountList.java index a229702ad..52c64dbe3 100644 --- a/src/com/fsck/k9/activity/AccountList.java +++ b/src/com/fsck/k9/activity/AccountList.java @@ -73,16 +73,11 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL List accounts = new ArrayList(); if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) { - BaseAccount integratedInboxAccount = new SearchAccount(this, true, null, null); - integratedInboxAccount.setDescription(getString(R.string.integrated_inbox_title)); - integratedInboxAccount.setEmail(getString(R.string.integrated_inbox_detail)); + BaseAccount unifiedInboxAccount = SearchAccount.createUnifiedInboxAccount(this); + BaseAccount allMessagesAccount = SearchAccount.createAllMessagesAccount(this); - BaseAccount unreadAccount = new SearchAccount(this, false, null, null); - unreadAccount.setDescription(getString(R.string.search_all_messages_title)); - unreadAccount.setEmail(getString(R.string.search_all_messages_detail)); - - accounts.add(integratedInboxAccount); - accounts.add(unreadAccount); + accounts.add(unifiedInboxAccount); + accounts.add(allMessagesAccount); } accounts.addAll(Arrays.asList(realAccounts)); diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 031785e62..339494044 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -382,16 +382,11 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } /** - * Creates and initializes the special accounts ('Integrated Inbox' and 'All Messages') + * Creates and initializes the special accounts ('Unified Inbox' and 'All Messages') */ private void createSpecialAccounts() { - unreadAccount = new SearchAccount(this, false, null, null); - unreadAccount.setDescription(getString(R.string.search_all_messages_title)); - unreadAccount.setEmail(getString(R.string.search_all_messages_detail)); - - integratedInboxAccount = new SearchAccount(this, true, null, null); - integratedInboxAccount.setDescription(getString(R.string.integrated_inbox_title)); - integratedInboxAccount.setEmail(getString(R.string.integrated_inbox_detail)); + integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(this); + unreadAccount = SearchAccount.createAllMessagesAccount(this); } @SuppressWarnings("unchecked") diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java index f2c1ec693..50220c31c 100644 --- a/src/com/fsck/k9/provider/MessageProvider.java +++ b/src/com/fsck/k9/provider/MessageProvider.java @@ -60,7 +60,7 @@ public class MessageProvider extends ContentProvider { *

Type: TEXT

*/ String SENDER = "sender"; - + /** *

Type: TEXT

*/ @@ -245,7 +245,7 @@ public class MessageProvider extends ContentProvider { final BlockingQueue> queue = new SynchronousQueue>(); // new code for integrated inbox, only execute this once as it will be processed afterwards via the listener - final SearchAccount integratedInboxAccount = new SearchAccount(getContext(), true, null, null); + final SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(getContext()); final MessagingController msgController = MessagingController.getInstance(K9.app); msgController.searchLocalMessages(integratedInboxAccount, null, From f1baa8f461c92b4d489e7ac52e0c91cef16eed0f Mon Sep 17 00:00:00 2001 From: m0viefreak Date: Fri, 2 Mar 2012 05:16:58 +0100 Subject: [PATCH 21/80] invalidate message content WebView when the message header changes This fixes redraw issues where text appears doubled or is cut off --- src/com/fsck/k9/view/MessageHeader.java | 17 +++++++++++++++++ src/com/fsck/k9/view/SingleMessageView.java | 10 +++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/view/MessageHeader.java b/src/com/fsck/k9/view/MessageHeader.java index a3f10afad..ed51d70d4 100644 --- a/src/com/fsck/k9/view/MessageHeader.java +++ b/src/com/fsck/k9/view/MessageHeader.java @@ -63,6 +63,8 @@ public class MessageHeader extends ScrollView implements OnClickListener { private ImageView mShowAdditionalHeadersIcon; private SavedState mSavedState; + private OnLayoutChangedListener mOnLayoutChangedListener; + /** * Pair class is only available since API Level 5, so we need * this helper class unfortunately @@ -272,6 +274,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { } else { showAdditionalHeaders(); } + layoutChanged(); } private List getAdditionalHeaders(final Message message) @@ -378,4 +381,18 @@ public class MessageHeader extends ScrollView implements OnClickListener { out.writeInt((this.additionalHeadersVisible) ? 1 : 0); } } + + public interface OnLayoutChangedListener { + void onLayoutChanged(); + } + + public void setOnLayoutChangedListener(OnLayoutChangedListener listener) { + mOnLayoutChangedListener = listener; + } + + private void layoutChanged() { + if (mOnLayoutChangedListener != null) { + mOnLayoutChangedListener.onLayoutChanged(); + } + } } diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java index b1e0980e9..a19796d8c 100644 --- a/src/com/fsck/k9/view/SingleMessageView.java +++ b/src/com/fsck/k9/view/SingleMessageView.java @@ -33,7 +33,8 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage; import java.util.List; -public class SingleMessageView extends LinearLayout implements OnClickListener { +public class SingleMessageView extends LinearLayout implements OnClickListener, + MessageHeader.OnLayoutChangedListener { private boolean mScreenReaderEnabled; private MessageCryptoView mCryptoView; private MessageWebView mMessageContentView; @@ -65,6 +66,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener { mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container); mHeaderContainer = (MessageHeader) findViewById(R.id.header_container); + mHeaderContainer.setOnLayoutChangedListener(this); mAttachmentsContainer = findViewById(R.id.attachments_container); mInsideAttachmentsContainer = (LinearLayout) findViewById(R.id.inside_attachments_container); @@ -508,6 +510,12 @@ public class SingleMessageView extends LinearLayout implements OnClickListener { mSavedState = savedState; } + public void onLayoutChanged() { + if (mMessageContentView != null) { + mMessageContentView.invalidate(); + } + } + static class SavedState extends BaseSavedState { boolean attachmentViewVisible; boolean hiddenAttachmentsVisible; From 8d12244a9c23da0b5146afa418f75a4b7e49747d Mon Sep 17 00:00:00 2001 From: m0viefreak Date: Fri, 2 Mar 2012 05:41:01 +0100 Subject: [PATCH 22/80] Message header changes * remove expand/collapse arrows, instead use the background area of the header to toggle * allow expanding of To: and Cc: texts when too long and cut off by clicking on them --- ...xml => message_view_header_background.xml} | 0 res/layout/message_view_header.xml | 50 ++----------------- src/com/fsck/k9/view/MessageHeader.java | 37 ++++++++++---- 3 files changed, 31 insertions(+), 56 deletions(-) rename res/drawable/{separator_area_background.xml => message_view_header_background.xml} (100%) diff --git a/res/drawable/separator_area_background.xml b/res/drawable/message_view_header_background.xml similarity index 100% rename from res/drawable/separator_area_background.xml rename to res/drawable/message_view_header_background.xml diff --git a/res/layout/message_view_header.xml b/res/layout/message_view_header.xml index fe5c486a0..5b4d0d591 100644 --- a/res/layout/message_view_header.xml +++ b/res/layout/message_view_header.xml @@ -17,7 +17,7 @@ android:layout_height="wrap_content" android:stretchColumns="1" android:shrinkColumns="1" - android:background="@color/message_view_header_background"> + android:background="@drawable/message_view_header_background"> @@ -194,51 +194,11 @@ - - - - - - - - - - - - - - - - - + android:layout_height="1.5dp" + android:background="#59000000"/> getAdditionalHeaders(final Message message) throws MessagingException { List additionalHeaders = new LinkedList(); From 78615f878d4ea28201e79652bd2c8694a09980a4 Mon Sep 17 00:00:00 2001 From: ashley willis Date: Mon, 5 Mar 2012 15:17:31 -0600 Subject: [PATCH 23/80] added From: addresses on reply all when Reply-To: is set. don't repeat address in To: field if it's already included in replyToAddresses. --- src/com/fsck/k9/activity/MessageCompose.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java index 927078cf2..8ecc78a08 100644 --- a/src/com/fsck/k9/activity/MessageCompose.java +++ b/src/com/fsck/k9/activity/MessageCompose.java @@ -2289,8 +2289,15 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc } if (ACTION_REPLY_ALL.equals(action)) { + if (message.getReplyTo().length > 0) { + for (Address address : message.getFrom()) { + if (!mAccount.isAnIdentity(address)) { + addAddress(mToView, address); + } + } + } for (Address address : message.getRecipients(RecipientType.TO)) { - if (!mAccount.isAnIdentity(address)) { + if (!mAccount.isAnIdentity(address) && !Utility.arrayContains(replyToAddresses, address)) { addAddress(mToView, address); } From faf9dc6e0263e7cb3df1d09ebdeea6fb85777df8 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 6 Mar 2012 04:26:18 +0100 Subject: [PATCH 24/80] Restored show/more less indicator in the message header --- res/layout/message_view_header.xml | 62 ++++++++++++++++++++++++- src/com/fsck/k9/view/MessageHeader.java | 11 ++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/res/layout/message_view_header.xml b/res/layout/message_view_header.xml index 5b4d0d591..19bd2a52d 100644 --- a/res/layout/message_view_header.xml +++ b/res/layout/message_view_header.xml @@ -30,6 +30,7 @@ @@ -134,14 +135,56 @@ + + - + + + + + + + + + + + + + + @@ -188,6 +231,21 @@ android:textColor="?android:attr/textColorPrimary" android:textAppearance="?android:attr/textAppearanceSmall"/> + + + + + diff --git a/src/com/fsck/k9/view/MessageHeader.java b/src/com/fsck/k9/view/MessageHeader.java index f3f3b9a32..277885b95 100644 --- a/src/com/fsck/k9/view/MessageHeader.java +++ b/src/com/fsck/k9/view/MessageHeader.java @@ -59,6 +59,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { private Account mAccount; private FontSizes mFontSizes = K9.getFontSizes(); private Contacts mContacts; + private ImageView mShowAdditionalHeadersIcon; private SavedState mSavedState; private OnLayoutChangedListener mOnLayoutChangedListener; @@ -98,6 +99,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { mDateView = (TextView) findViewById(R.id.date); mTimeView = (TextView) findViewById(R.id.time); mFlagged = (CheckBox) findViewById(R.id.flagged); + mShowAdditionalHeadersIcon = (ImageView) findViewById(R.id.show_additional_headers_icon); defaultSubjectColor = mSubjectView.getCurrentTextColor(); mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewSubject()); @@ -113,10 +115,13 @@ public class MessageHeader extends ScrollView implements OnClickListener { ((TextView) findViewById(R.id.to_label)).setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTo()); ((TextView) findViewById(R.id.cc_label)).setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewCC()); - mToView.setOnClickListener(this); - mCcView.setOnClickListener(this); mFromView.setOnClickListener(this); findViewById(R.id.top_container).setOnClickListener(this); + + TextView dummyDateView = (TextView) findViewById(R.id.dummy_date); + TextView dummyTimeView = (TextView) findViewById(R.id.dummy_time); + dummyTimeView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTime()); + dummyDateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewDate()); } @Override @@ -168,6 +173,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { private void hideAdditionalHeaders() { mAdditionalHeadersView.setVisibility(View.GONE); mAdditionalHeadersView.setText(""); + mShowAdditionalHeadersIcon.setImageResource(R.drawable.show_more); } @@ -186,6 +192,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { // Show the additional headers that we have got. populateAdditionalHeadersView(additionalHeaders); mAdditionalHeadersView.setVisibility(View.VISIBLE); + mShowAdditionalHeadersIcon.setImageResource(R.drawable.show_less); } if (!allHeadersDownloaded) { /* From 4721d92e7909fd00619a02571916c438ec5b395b Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 7 Mar 2012 21:40:41 +0100 Subject: [PATCH 25/80] Avoid some instances of autoboxing This will hopefully allow us to compile with "Android Java IDE". --- src/com/fsck/k9/activity/MessageCompose.java | 10 +++++----- src/com/fsck/k9/controller/MessagingController.java | 10 ++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java index 8ecc78a08..e14c34c8e 100644 --- a/src/com/fsck/k9/activity/MessageCompose.java +++ b/src/com/fsck/k9/activity/MessageCompose.java @@ -2427,19 +2427,19 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc updateFrom(); Integer bodyLength = k9identity.get(IdentityField.LENGTH) != null - ? Integer.parseInt(k9identity.get(IdentityField.LENGTH)) + ? Integer.valueOf(k9identity.get(IdentityField.LENGTH)) : 0; Integer bodyOffset = k9identity.get(IdentityField.OFFSET) != null - ? Integer.parseInt(k9identity.get(IdentityField.OFFSET)) + ? Integer.valueOf(k9identity.get(IdentityField.OFFSET)) : 0; Integer bodyFooterOffset = k9identity.get(IdentityField.FOOTER_OFFSET) != null - ? Integer.parseInt(k9identity.get(IdentityField.FOOTER_OFFSET)) + ? Integer.valueOf(k9identity.get(IdentityField.FOOTER_OFFSET)) : null; Integer bodyPlainLength = k9identity.get(IdentityField.PLAIN_LENGTH) != null - ? Integer.parseInt(k9identity.get(IdentityField.PLAIN_LENGTH)) + ? Integer.valueOf(k9identity.get(IdentityField.PLAIN_LENGTH)) : null; Integer bodyPlainOffset = k9identity.get(IdentityField.PLAIN_OFFSET) != null - ? Integer.parseInt(k9identity.get(IdentityField.PLAIN_OFFSET)) + ? Integer.valueOf(k9identity.get(IdentityField.PLAIN_OFFSET)) : null; mQuoteStyle = k9identity.get(IdentityField.QUOTE_STYLE) != null ? QuoteStyle.valueOf(k9identity.get(IdentityField.QUOTE_STYLE)) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 023d19f60..250e3fb3d 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -629,7 +629,7 @@ public class MessagingController implements Runnable { Log.i(K9.LOG_TAG, "searchLocalMessages (" + "accountUuids=" + Utility.combine(accountUuids, ',') + ", folderNames = " + Utility.combine(folderNames, ',') - + ", messages.size() = " + (messages != null ? messages.length : null) + + ", messages.size() = " + (messages != null ? messages.length : -1) + ", query = " + query + ", integrate = " + integrate + ", requiredFlags = " + Utility.combine(requiredFlags, ',') @@ -4182,7 +4182,13 @@ public class MessagingController implements Runnable { NotificationSetting n = account.getNotificationSetting(); - configureNotification(notif, (n.shouldRing() ? n.getRingtone() : null), (n.shouldVibrate() ? n.getVibration() : null), (n.isLed() ? n.getLedColor() : null), K9.NOTIFICATION_LED_BLINK_SLOW, ringAndVibrate); + configureNotification( + notif, + (n.shouldRing()) ? n.getRingtone() : null, + (n.shouldVibrate()) ? n.getVibration() : null, + (n.isLed()) ? Integer.valueOf(n.getLedColor()) : null, + K9.NOTIFICATION_LED_BLINK_SLOW, + ringAndVibrate); notifMgr.notify(account.getAccountNumber(), notif); } From ee34344d30fded0c3cba2b489a3462b271ff97a8 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 7 Mar 2012 21:52:47 +0100 Subject: [PATCH 26/80] Upgrade to SDK 15 --- project.properties | 2 +- src/com/fsck/k9/preferences/Editor.java | 6 ++++ src/com/fsck/k9/preferences/Storage.java | 7 ++++ src/com/fsck/k9/provider/MessageProvider.java | 32 +++++++++++-------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/project.properties b/project.properties index 213821e1f..46fb34716 100644 --- a/project.properties +++ b/project.properties @@ -11,5 +11,5 @@ split.density=false java.encoding=utf8 # Project target. -target=android-9 +target=android-15 extensible.libs.classpath=compile-only-libs diff --git a/src/com/fsck/k9/preferences/Editor.java b/src/com/fsck/k9/preferences/Editor.java index 412d14ff8..88479d0e2 100644 --- a/src/com/fsck/k9/preferences/Editor.java +++ b/src/com/fsck/k9/preferences/Editor.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; public class Editor implements android.content.SharedPreferences.Editor { private Storage storage; @@ -138,4 +139,9 @@ public class Editor implements android.content.SharedPreferences.Editor { return this; } + @Override + public android.content.SharedPreferences.Editor putStringSet(String arg0, Set arg1) { + throw new RuntimeException("Not implemented"); + } + } diff --git a/src/com/fsck/k9/preferences/Storage.java b/src/com/fsck/k9/preferences/Storage.java index 34c978754..b9041c8f6 100644 --- a/src/com/fsck/k9/preferences/Storage.java +++ b/src/com/fsck/k9/preferences/Storage.java @@ -15,6 +15,7 @@ import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -399,4 +400,10 @@ public class Storage implements SharedPreferences { Log.e(K9.LOG_TAG, "Error writing key '" + key + "', value = '" + value + "'"); } } + + + @Override + public Set getStringSet(String arg0, Set arg1) { + throw new RuntimeException("Not implemented"); + } } diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java index 50220c31c..0557575af 100644 --- a/src/com/fsck/k9/provider/MessageProvider.java +++ b/src/com/fsck/k9/provider/MessageProvider.java @@ -1,18 +1,5 @@ package com.fsck.k9.provider; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.Semaphore; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; @@ -46,6 +33,19 @@ import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.store.LocalStore; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + public class MessageProvider extends ContentProvider { public static interface MessageColumns extends BaseColumns { @@ -714,6 +714,12 @@ public class MessageProvider extends ContentProvider { checkClosed(); mCursor.unregisterDataSetObserver(observer); } + + @Override + public int getType(int columnIndex) { + checkClosed(); + return mCursor.getType(columnIndex); + } } protected class ThrottlingQueryHandler implements QueryHandler { From 7163d39091b2a32724d9767b5ebf92dcb786a90b Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 9 Mar 2012 21:50:26 +0100 Subject: [PATCH 27/80] Change ImapException to always be a permanent error This way IMAP commands that get anything but an "OK" response are never tried again (pending actions). --- src/com/fsck/k9/mail/store/ImapStore.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 6a77db634..008d8d45a 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -2735,13 +2735,8 @@ public class ImapStore extends Store { private static final long serialVersionUID = 3725007182205882394L; String mAlertText; - public ImapException(String message, String alertText, Throwable throwable) { - super(message, throwable); - this.mAlertText = alertText; - } - public ImapException(String message, String alertText) { - super(message); + super(message, true); this.mAlertText = alertText; } From 0cb4207ef73027e86feee4400647ad8f10f777d9 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 9 Mar 2012 21:51:48 +0100 Subject: [PATCH 28/80] IMAP: don't create the destination folder when copying messages --- src/com/fsck/k9/mail/store/ImapStore.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 008d8d45a..043920aa6 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -1101,15 +1101,6 @@ public class ImapStore extends Store { try { String remoteDestName = encodeString(encodeFolderName(iFolder.getPrefixedName())); - if (!exists(remoteDestName)) { - // If the remote trash folder doesn't exist we try to create it. - if (K9.DEBUG) { - Log.i(K9.LOG_TAG, "Attempting to create remote folder '" + remoteDestName + - "' for " + getLogId()); - } - iFolder.create(FolderType.HOLDS_MESSAGES); - } - //TODO: Split this into multiple commands if the command exceeds a certain length. mConnection.sendCommand(String.format("UID COPY %s %s", Utility.combine(uids, ','), From dda072eff65c135a59da78b8b04482471e9ad199 Mon Sep 17 00:00:00 2001 From: ashley willis Date: Sun, 11 Mar 2012 14:31:35 -0500 Subject: [PATCH 29/80] Upgrade to SDK 15 --- ant.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ant.properties b/ant.properties index cfc6e1ad4..6be7f3220 100644 --- a/ant.properties +++ b/ant.properties @@ -19,5 +19,5 @@ split.density=false java.encoding=utf8 # Project target. -target=android-9 +target=android-15 extensible.libs.classpath=compile-only-libs From f9a30d1822b0de034bab4d08b1e9392b7d7e69e4 Mon Sep 17 00:00:00 2001 From: ashley willis Date: Sun, 11 Mar 2012 17:48:56 -0500 Subject: [PATCH 30/80] added sort by arrival option. --- res/menu/message_list_option.xml | 4 ++++ res/values/strings.xml | 1 + src/com/fsck/k9/activity/MessageInfoHolder.java | 1 + src/com/fsck/k9/activity/MessageList.java | 16 +++++++++++++++- .../fsck/k9/controller/MessagingController.java | 1 + src/com/fsck/k9/helper/MessageHelper.java | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/res/menu/message_list_option.xml b/res/menu/message_list_option.xml index 2aa550a0f..b8bbf03a7 100644 --- a/res/menu/message_list_option.xml +++ b/res/menu/message_list_option.xml @@ -15,6 +15,10 @@ android:id="@+id/set_sort_date" android:title="@string/sort_by_date" /> + Sort by... Date + Arrival Sender Subject Star diff --git a/src/com/fsck/k9/activity/MessageInfoHolder.java b/src/com/fsck/k9/activity/MessageInfoHolder.java index e19fb953a..baee99b9a 100644 --- a/src/com/fsck/k9/activity/MessageInfoHolder.java +++ b/src/com/fsck/k9/activity/MessageInfoHolder.java @@ -7,6 +7,7 @@ import com.fsck.k9.mail.store.LocalStore.LocalMessage; public class MessageInfoHolder { public String date; public Date compareDate; + public Date compareArrival; public String compareSubject; public CharSequence sender; public String senderAddress; diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index f874267f9..aec709cfe 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -185,6 +185,15 @@ public class MessageList } + public static class ArrivalComparator implements Comparator { + + @Override + public int compare(MessageInfoHolder object1, MessageInfoHolder object2) { + return object1.compareArrival.compareTo(object2.compareArrival); + } + + } + public static class SubjectComparator implements Comparator { @Override @@ -234,6 +243,7 @@ public class MessageList final Map> map = new EnumMap>(SORT_TYPE.class); map.put(SORT_TYPE.SORT_ATTACHMENT, new AttachmentComparator()); map.put(SORT_TYPE.SORT_DATE, new DateComparator()); + map.put(SORT_TYPE.SORT_ARRIVAL, new ArrivalComparator()); map.put(SORT_TYPE.SORT_FLAGGED, new FlaggedComparator()); map.put(SORT_TYPE.SORT_SENDER, new SenderComparator()); map.put(SORT_TYPE.SORT_SUBJECT, new SubjectComparator()); @@ -460,7 +470,7 @@ public class MessageList { // add the date comparator if not already specified - if (sortType != SORT_TYPE.SORT_DATE) { + if (sortType != SORT_TYPE.SORT_DATE && sortType != SORT_TYPE.SORT_ARRIVAL) { final Comparator comparator = SORT_COMPARATORS.get(SORT_TYPE.SORT_DATE); if (sortDateAscending) { chain.add(comparator); @@ -1439,6 +1449,10 @@ public class MessageList changeSort(SORT_TYPE.SORT_DATE); return true; } + case R.id.set_sort_arrival: { + changeSort(SORT_TYPE.SORT_ARRIVAL); + return true; + } case R.id.set_sort_subject: { changeSort(SORT_TYPE.SORT_SUBJECT); return true; diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 250e3fb3d..33acd96f1 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -146,6 +146,7 @@ public class MessagingController implements Runnable { public enum SORT_TYPE { SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false), + SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false), SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true), SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true), SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true), diff --git a/src/com/fsck/k9/helper/MessageHelper.java b/src/com/fsck/k9/helper/MessageHelper.java index 43b208a77..018b481e2 100644 --- a/src/com/fsck/k9/helper/MessageHelper.java +++ b/src/com/fsck/k9/helper/MessageHelper.java @@ -49,6 +49,7 @@ public class MessageHelper { try { LocalMessage message = (LocalMessage) m; target.message = message; + target.compareArrival = message.getInternalDate(); target.compareDate = message.getSentDate(); if (target.compareDate == null) { target.compareDate = message.getInternalDate(); From 561d2a1a44ae6e9615b9a9c04d8a040b4a724690 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 12 Mar 2012 17:44:53 +0100 Subject: [PATCH 31/80] Added unit test for MimeUtility.getHeaderParameter() --- .../k9/mail/internet/MimeUtilityTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/src/com/fsck/k9/mail/internet/MimeUtilityTest.java diff --git a/tests/src/com/fsck/k9/mail/internet/MimeUtilityTest.java b/tests/src/com/fsck/k9/mail/internet/MimeUtilityTest.java new file mode 100644 index 000000000..8bf122395 --- /dev/null +++ b/tests/src/com/fsck/k9/mail/internet/MimeUtilityTest.java @@ -0,0 +1,39 @@ +package com.fsck.k9.mail.internet; + +import android.test.AndroidTestCase; + +public class MimeUtilityTest extends AndroidTestCase { + + public void testGetHeaderParameter() { + String result; + + /* Test edge cases */ + result = MimeUtility.getHeaderParameter(";", null); + assertEquals(null, result); + + result = MimeUtility.getHeaderParameter("name", "name"); + assertEquals(null, result); + + result = MimeUtility.getHeaderParameter("name=", "name"); + assertEquals("", result); + + result = MimeUtility.getHeaderParameter("name=\"", "name"); + assertEquals("\"", result); + + /* Test expected cases */ + result = MimeUtility.getHeaderParameter("name=value", "name"); + assertEquals("value", result); + + result = MimeUtility.getHeaderParameter("name = value", "name"); + assertEquals("value", result); + + result = MimeUtility.getHeaderParameter("name=\"value\"", "name"); + assertEquals("value", result); + + result = MimeUtility.getHeaderParameter("name = \"value\"" , "name"); + assertEquals("value", result); + + result = MimeUtility.getHeaderParameter("name=\"\"", "name"); + assertEquals("", result); + } +} From aeb0220e56b4717e4af262097f391d42fd3a3a4b Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 12 Mar 2012 17:45:34 +0100 Subject: [PATCH 32/80] Fixed MimeUtility.getHeaderParameter() to not crash on unexpected input --- src/com/fsck/k9/mail/internet/MimeUtility.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 405243d4b..0b8caf71f 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -953,16 +953,20 @@ public class MimeUtility { } header = header.replaceAll("\r|\n", ""); String[] parts = header.split(";"); - if (name == null) { + if (name == null && parts.length > 0) { return parts[0]; } for (String part : parts) { if (part.trim().toLowerCase(Locale.US).startsWith(name.toLowerCase(Locale.US))) { - String parameter = part.split("=", 2)[1].trim(); - if (parameter.startsWith("\"") && parameter.endsWith("\"")) { - return parameter.substring(1, parameter.length() - 1); - } else { - return parameter; + String[] partParts = part.split("=", 2); + if (partParts.length == 2) { + String parameter = partParts[1].trim(); + int len = parameter.length(); + if (len >= 2 && parameter.startsWith("\"") && parameter.endsWith("\"")) { + return parameter.substring(1, len - 1); + } else { + return parameter; + } } } } From f181e923ca8bb4f75f250f6f56f8b4409e0a4ae3 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 15 Mar 2012 21:21:00 +0100 Subject: [PATCH 33/80] Don't modify draft messages when storing them in the database --- .../fsck/k9/mail/internet/MimeUtility.java | 57 +++++++++++++++++++ src/com/fsck/k9/mail/store/LocalStore.java | 27 +++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 0b8caf71f..0405fd567 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -3260,4 +3260,61 @@ public class MimeUtility { return charset; } + + public static ViewableContainer extractPartsFromDraft(Message message) + throws MessagingException { + + Body body = message.getBody(); + if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) { + MimeMultipart multipart = (MimeMultipart) body; + + ViewableContainer container; + int count = multipart.getCount(); + if (count >= 1) { + // The first part is either a text/plain or a multipart/alternative + BodyPart firstPart = multipart.getBodyPart(0); + container = extractTextual(firstPart); + + // The rest should be attachments + for (int i = 1; i < count; i++) { + BodyPart bodyPart = multipart.getBodyPart(i); + container.attachments.add(bodyPart); + } + } else { + container = new ViewableContainer(null, null, new ArrayList()); + } + + return container; + } + + return extractTextual(message); + } + + private static ViewableContainer extractTextual(Part part) throws MessagingException { + String text = null; + String html = null; + List attachments = new ArrayList(); + + Body firstBody = part.getBody(); + if (part.isMimeType("text/plain") && + firstBody instanceof TextBody) { + text = ((TextBody) firstBody).getText(); + } else if (part.isMimeType("multipart/alternative") && + firstBody instanceof MimeMultipart) { + MimeMultipart multipart = (MimeMultipart) firstBody; + for (int i = 0, count = multipart.getCount(); i < count; i++) { + BodyPart bodyPart = multipart.getBodyPart(i); + if (bodyPart.getBody() instanceof TextBody) { + TextBody textBody = (TextBody) bodyPart.getBody(); + if (text == null && bodyPart.isMimeType("text/plain")) { + text = textBody.getText(); + } else if (html == null && bodyPart.isMimeType("text/html")) { + html = textBody.getText(); + } + } + } + } + + return new ViewableContainer(text, html, attachments); + } } diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index f9d769511..64d89610c 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2134,12 +2134,29 @@ public class LocalStore extends Store implements Serializable { deleteAttachments(message.getUid()); } - ViewableContainer container = - MimeUtility.extractTextAndAttachments(mApplication, message); + boolean isDraft = (message.getHeader(K9.IDENTITY_HEADER) != null); - List attachments = container.attachments; - String text = container.text; - String html = HtmlConverter.convertEmoji2Img(container.html); + List attachments; + String text; + String html; + if (isDraft) { + // Don't modify the text/plain or text/html part of our own + // draft messages because this will cause the values stored in + // the identity header to be wrong. + ViewableContainer container = + MimeUtility.extractPartsFromDraft(message); + + text = container.text; + html = container.html; + attachments = container.attachments; + } else { + ViewableContainer container = + MimeUtility.extractTextAndAttachments(mApplication, message); + + attachments = container.attachments; + text = container.text; + html = HtmlConverter.convertEmoji2Img(container.html); + } String preview = calculateContentPreview(text); From a48adafbbc99ce2b0ad275060f2045d1f8199328 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 16 Mar 2012 22:56:09 +0100 Subject: [PATCH 34/80] Don't use null for 'text' and 'html' in ViewableContainer --- src/com/fsck/k9/mail/internet/MimeUtility.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 0405fd567..1c43d3b58 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -3281,7 +3281,7 @@ public class MimeUtility { container.attachments.add(bodyPart); } } else { - container = new ViewableContainer(null, null, new ArrayList()); + container = new ViewableContainer("", "", new ArrayList()); } return container; @@ -3291,8 +3291,8 @@ public class MimeUtility { } private static ViewableContainer extractTextual(Part part) throws MessagingException { - String text = null; - String html = null; + String text = ""; + String html = ""; List attachments = new ArrayList(); Body firstBody = part.getBody(); From dbf38dae653aa2fdfe2372cec2ab2e19bea5f5c7 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 17 Mar 2012 00:30:40 +0100 Subject: [PATCH 35/80] Fixed the change of the previous commit --- src/com/fsck/k9/mail/internet/MimeUtility.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 1c43d3b58..20486232c 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -3306,9 +3306,9 @@ public class MimeUtility { BodyPart bodyPart = multipart.getBodyPart(i); if (bodyPart.getBody() instanceof TextBody) { TextBody textBody = (TextBody) bodyPart.getBody(); - if (text == null && bodyPart.isMimeType("text/plain")) { + if ("".equals(text) && bodyPart.isMimeType("text/plain")) { text = textBody.getText(); - } else if (html == null && bodyPart.isMimeType("text/html")) { + } else if ("".equals(html) && bodyPart.isMimeType("text/html")) { html = textBody.getText(); } } From 3fa8081e88e0b035377c721fd21676a735d82baf Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 17 Mar 2012 03:19:09 +0100 Subject: [PATCH 36/80] Fixed MimeUtility.extractTextual() when loading messages from the server --- src/com/fsck/k9/mail/internet/MimeUtility.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 20486232c..eb6948c11 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -3304,12 +3304,12 @@ public class MimeUtility { MimeMultipart multipart = (MimeMultipart) firstBody; for (int i = 0, count = multipart.getCount(); i < count; i++) { BodyPart bodyPart = multipart.getBodyPart(i); - if (bodyPart.getBody() instanceof TextBody) { - TextBody textBody = (TextBody) bodyPart.getBody(); - if ("".equals(text) && bodyPart.isMimeType("text/plain")) { - text = textBody.getText(); - } else if ("".equals(html) && bodyPart.isMimeType("text/html")) { - html = textBody.getText(); + String bodyText = getTextFromPart(bodyPart); + if (bodyText != null) { + if (text.length() == 0 && bodyPart.isMimeType("text/plain")) { + text = bodyText; + } else if (html.length() == 0 && bodyPart.isMimeType("text/html")) { + html = bodyText; } } } From f9a35aeaeeae67bd88c3b253c25d06832ca7da73 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 17 Mar 2012 04:15:30 +0100 Subject: [PATCH 37/80] Replace CRLF with LF when loading drafts This is necessary because we save the offset and length of the user- supplied text in the identity header. These values are then later used to split the draft in user text and quoted message. When calculating these values we operate on a string with LF line endings. Ideally we want to do the reverse operation on the same string, but when saving the message to the server LF is converted to CRLF to create RFC-conforming messages. This is only a hack and will probably be the cause of more trouble in the future. A better solution would be to make the identity header more robust or get rid of it entirely. --- .../fsck/k9/mail/internet/MimeUtility.java | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index eb6948c11..842a7cc8d 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -3296,9 +3296,11 @@ public class MimeUtility { List attachments = new ArrayList(); Body firstBody = part.getBody(); - if (part.isMimeType("text/plain") && - firstBody instanceof TextBody) { - text = ((TextBody) firstBody).getText(); + if (part.isMimeType("text/plain")) { + String bodyText = getTextFromPart(part); + if (bodyText != null) { + text = fixDraftTextBody(bodyText); + } } else if (part.isMimeType("multipart/alternative") && firstBody instanceof MimeMultipart) { MimeMultipart multipart = (MimeMultipart) firstBody; @@ -3307,9 +3309,9 @@ public class MimeUtility { String bodyText = getTextFromPart(bodyPart); if (bodyText != null) { if (text.length() == 0 && bodyPart.isMimeType("text/plain")) { - text = bodyText; + text = fixDraftTextBody(bodyText); } else if (html.length() == 0 && bodyPart.isMimeType("text/html")) { - html = bodyText; + html = fixDraftTextBody(bodyText); } } } @@ -3317,4 +3319,21 @@ public class MimeUtility { return new ViewableContainer(text, html, attachments); } + + /** + * Fix line endings of text bodies in draft messages. + * + *

+ * We create drafts with LF line endings. The values in the identity header are based on that. + * So we replace CRLF with LF when loading messages (from the server). + *

+ * + * @param text + * The body text with CRLF line endings + * + * @return The text with LF line endings + */ + private static String fixDraftTextBody(String text) { + return text.replace("\r\n", "\n"); + } } From 8192d54cce6d5f0796ad9a806627dd9b11249238 Mon Sep 17 00:00:00 2001 From: Koji Arai Date: Sat, 17 Mar 2012 23:08:14 +0900 Subject: [PATCH 38/80] Updated Japanese translation. catch up with f9a30d1. --- res/values-ja/strings.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 55815636e..794a17cf2 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -287,8 +287,11 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール SDカードに添付ファイルを保存できません \"画像表示\"ボタンを押下すると描画します 画像表示 - 添付取込中 - 添付ファイルのビューワー見つけられません .%s + メッセージ表示 + 添付ファイル表示 + 他… + 添付ファイル取得中 + %sのビューワーが見つかりません すべてダウンロード @@ -777,6 +780,7 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール 並べ替え... 日付 + 受信順 送信者 件名 フラグ From 39f2138292225c1b904c5bfbeec41e58c20d100b Mon Sep 17 00:00:00 2001 From: Koji Arai Date: Sun, 4 Mar 2012 09:47:29 +0900 Subject: [PATCH 39/80] added a Japanese provider "auone.jp" --- res/values-ja/strings.xml | 4 +++- res/values/strings.xml | 1 + res/xml/providers.xml | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 794a17cf2..4c678800b 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -799,7 +799,9 @@ K-9 Mail セットアップにようこそ。\nK-9 は標準のAndroidメール このプログラムでPOPアクセスが許可されているのは一部の「Plus」アカウントだけです。有料の「Plus」アカウントがなければ、正しいメールアドレスとパスワードを入力してもログインできません。これらのアカウントにはブラウザからアクセスしてください。 - Yahoo! JapanでPOP3アクセスを使う場合は、Yahoo!メールサイトの「メールの設定」にてPOPアクセスが許可されていることを確認してください。 + Yahoo! JapanでPOP3アクセスを行う場合は、Yahoo!メールサイトの「メールの設定」にてPOPアクセスが許可されていることを確認してください。 + + au oneでIMAPアクセスを行う場合は、au oneポータルサイトの「メール」→「設定」ページにて「IMAPを有効にする」をチェックしてください。 証明書が無効です 許可 diff --git a/res/values/strings.xml b/res/values/strings.xml index d7ad77b8d..820960b66 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -805,6 +805,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin \"Plus\" account. Please launch the Web browser to gain access to these mail accounts.
If you would like to use POP3 for this provider, You should permit to use POP3 on Yahoo mail settings page. + If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on au one mail settings page. If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Naver mail settings page. If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Hanmail(Daum) mail settings page. If you would like to use IMAP or POP3 for this provider, You should permit to use IMAP or POP3 on Paran mail settings page. diff --git a/res/xml/providers.xml b/res/xml/providers.xml index a00108996..d94d929a7 100644 --- a/res/xml/providers.xml +++ b/res/xml/providers.xml @@ -283,6 +283,11 @@ + + + + Date: Wed, 29 Feb 2012 00:51:02 +0900 Subject: [PATCH 40/80] avoid NPE. address may be null when the parser is failed. --- src/com/fsck/k9/mail/internet/MimeUtility.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 842a7cc8d..13ebc9304 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -2182,6 +2182,8 @@ public class MimeUtility { } private static String getJisVariantFromAddress(String address) { + if (address == null) + return null; if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") || isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) return "docomo"; From 10c37942a632dc1a05cbf7624e0739a7e624ae02 Mon Sep 17 00:00:00 2001 From: Koji Arai Date: Wed, 7 Mar 2012 00:56:33 +0900 Subject: [PATCH 41/80] Added two domains handle docomo emoji --- src/com/fsck/k9/mail/internet/MimeUtility.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 13ebc9304..e1908670d 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -2185,7 +2185,8 @@ public class MimeUtility { if (address == null) return null; if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") || - isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) + isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") || + isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp")) return "docomo"; else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") || isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp")) From ef01dc906b1b006a0bab045ebd426281407c2558 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 17 Mar 2012 18:27:17 +0100 Subject: [PATCH 42/80] Use AlertDialog's ListView on import instead of creating our own This fixes a display problem when using the dark theme. --- src/com/fsck/k9/activity/Accounts.java | 52 +++++++++++--------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 339494044..d31bced32 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -21,6 +21,7 @@ import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnMultiChoiceClickListener; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -45,12 +46,10 @@ import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; -import android.widget.CheckedTextView; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; -import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.ScrollView; @@ -58,7 +57,6 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemSelectedListener; import android.widget.CompoundButton.OnCheckedChangeListener; import com.fsck.k9.Account; @@ -1464,8 +1462,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static class ImportSelectionDialog implements NonConfigurationInstance { private ImportContents mImportContents; private Uri mUri; - private Dialog mDialog; - private ListView mImportSelectionView; + private AlertDialog mDialog; private SparseBooleanArray mSelection; @@ -1483,8 +1480,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC public boolean retain() { if (mDialog != null) { // Save the selection state of each list item - mSelection = mImportSelectionView.getCheckedItemPositions(); - mImportSelectionView = null; + mSelection = mDialog.getListView().getCheckedItemPositions(); mDialog.dismiss(); mDialog = null; @@ -1498,8 +1494,6 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC } public void show(final Accounts activity, SparseBooleanArray selection) { - final ListView importSelectionView = new ListView(activity); - mImportSelectionView = importSelectionView; List contents = new ArrayList(); if (mImportContents.globalSettings) { @@ -1510,23 +1504,15 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC contents.add(account.name); } - importSelectionView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - importSelectionView.setAdapter(new ArrayAdapter(activity, - android.R.layout.simple_list_item_checked, contents)); - importSelectionView.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int pos, long id) { - CheckedTextView ctv = (CheckedTextView)view; - ctv.setChecked(!ctv.isChecked()); - } - - @Override - public void onNothingSelected(AdapterView arg0) { /* Do nothing */ } - }); - + int count = contents.size(); + boolean[] checkedItems = new boolean[count]; if (selection != null) { - for (int i = 0, end = contents.size(); i < end; i++) { - importSelectionView.setItemChecked(i, selection.get(i)); + for (int i = 0; i < count; i++) { + checkedItems[i] = selection.get(i); + } + } else { + for (int i = 0; i < count; i++) { + checkedItems[i] = true; } } @@ -1534,23 +1520,29 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC //TODO: listview footer: "Select all" / "Select none" buttons? //TODO: listview footer: "Overwrite existing accounts?" checkbox + OnMultiChoiceClickListener listener = new OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, boolean isChecked) { + ((AlertDialog) dialog).getListView().setItemChecked(which, isChecked); + } + }; + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMultiChoiceItems(contents.toArray(new String[0]), checkedItems, listener); builder.setTitle(activity.getString(R.string.settings_import_selection)); - builder.setView(importSelectionView); builder.setInverseBackgroundForced(true); builder.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - ListAdapter adapter = importSelectionView.getAdapter(); - int count = adapter.getCount(); - SparseBooleanArray pos = importSelectionView.getCheckedItemPositions(); + ListView listView = ((AlertDialog) dialog).getListView(); + SparseBooleanArray pos = listView.getCheckedItemPositions(); boolean includeGlobals = mImportContents.globalSettings ? pos.get(0) : false; List accountUuids = new ArrayList(); int start = mImportContents.globalSettings ? 1 : 0; - for (int i = start; i < count; i++) { + for (int i = start, end = listView.getCount(); i < end; i++) { if (pos.get(i)) { accountUuids.add(mImportContents.accounts.get(i-start).uuid); } From 69ee6a48187d7c1cde3bc3cdb292f4858d7c5c56 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 17 Mar 2012 20:12:33 +0100 Subject: [PATCH 43/80] Avoid NullPointerException in Accounts.onCreateDialog() Also cleaned up code formatting of onCreateDialog() and onPrepareDialog(). --- src/com/fsck/k9/activity/Accounts.java | 195 ++++++++++++++----------- 1 file changed, 108 insertions(+), 87 deletions(-) diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index d31bced32..bb8d9a96a 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -918,107 +918,128 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC @Override public Dialog onCreateDialog(int id) { + // Android recreates our dialogs on configuration changes even when they have been + // dismissed. Make sure we have all information necessary before creating a new dialog. switch (id) { - case DIALOG_REMOVE_ACCOUNT: - return ConfirmationDialog.create(this, id, - R.string.account_delete_dlg_title, - getString(R.string.account_delete_dlg_instructions_fmt, - mSelectedContextAccount.getDescription()), - R.string.okay_action, - R.string.cancel_action, - new Runnable() { - @Override - public void run() { - if (mSelectedContextAccount instanceof Account) { - Account realAccount = (Account)mSelectedContextAccount; - try { - realAccount.getLocalStore().delete(); - } catch (Exception e) { - // Ignore, this may lead to localStores on sd-cards that are - // currently not inserted to be left - } - MessagingController.getInstance(getApplication()) - .notifyAccountCancel(Accounts.this, realAccount); - Preferences.getPreferences(Accounts.this).deleteAccount(realAccount); - K9.setServicesEnabled(Accounts.this); - refresh(); - } + case DIALOG_REMOVE_ACCOUNT: { + if (mSelectedContextAccount == null) { + return null; } - }); - case DIALOG_CLEAR_ACCOUNT: - return ConfirmationDialog.create(this, id, - R.string.account_clear_dlg_title, - getString(R.string.account_clear_dlg_instructions_fmt, - mSelectedContextAccount.getDescription()), - R.string.okay_action, - R.string.cancel_action, - new Runnable() { - @Override - public void run() { - if (mSelectedContextAccount instanceof Account) { - Account realAccount = (Account)mSelectedContextAccount; - mHandler.workingAccount(realAccount, R.string.clearing_account); - MessagingController.getInstance(getApplication()).clear(realAccount, null); - } + return ConfirmationDialog.create(this, id, + R.string.account_delete_dlg_title, + getString(R.string.account_delete_dlg_instructions_fmt, + mSelectedContextAccount.getDescription()), + R.string.okay_action, + R.string.cancel_action, + new Runnable() { + @Override + public void run() { + if (mSelectedContextAccount instanceof Account) { + Account realAccount = (Account) mSelectedContextAccount; + try { + realAccount.getLocalStore().delete(); + } catch (Exception e) { + // Ignore, this may lead to localStores on sd-cards that + // are currently not inserted to be left + } + MessagingController.getInstance(getApplication()) + .notifyAccountCancel(Accounts.this, realAccount); + Preferences.getPreferences(Accounts.this) + .deleteAccount(realAccount); + K9.setServicesEnabled(Accounts.this); + refresh(); + } + } + }); + } + case DIALOG_CLEAR_ACCOUNT: { + if (mSelectedContextAccount == null) { + return null; } - }); - case DIALOG_RECREATE_ACCOUNT: - return ConfirmationDialog.create(this, id, - R.string.account_recreate_dlg_title, - getString(R.string.account_recreate_dlg_instructions_fmt, - mSelectedContextAccount.getDescription()), - R.string.okay_action, - R.string.cancel_action, - new Runnable() { - @Override - public void run() { - if (mSelectedContextAccount instanceof Account) { - Account realAccount = (Account)mSelectedContextAccount; - mHandler.workingAccount(realAccount, R.string.recreating_account); - MessagingController.getInstance(getApplication()).recreate(realAccount, null); - } + return ConfirmationDialog.create(this, id, + R.string.account_clear_dlg_title, + getString(R.string.account_clear_dlg_instructions_fmt, + mSelectedContextAccount.getDescription()), + R.string.okay_action, + R.string.cancel_action, + new Runnable() { + @Override + public void run() { + if (mSelectedContextAccount instanceof Account) { + Account realAccount = (Account) mSelectedContextAccount; + mHandler.workingAccount(realAccount, + R.string.clearing_account); + MessagingController.getInstance(getApplication()) + .clear(realAccount, null); + } + } + }); + } + case DIALOG_RECREATE_ACCOUNT: { + if (mSelectedContextAccount == null) { + return null; } - }); - case DIALOG_NO_FILE_MANAGER: - return ConfirmationDialog.create(this, id, - R.string.import_dialog_error_title, - getString(R.string.import_dialog_error_message), - R.string.open_market, - R.string.close, - new Runnable() { - @Override - public void run() { - Uri uri = Uri.parse(ANDROID_MARKET_URL); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(intent); - } - }); + + return ConfirmationDialog.create(this, id, + R.string.account_recreate_dlg_title, + getString(R.string.account_recreate_dlg_instructions_fmt, + mSelectedContextAccount.getDescription()), + R.string.okay_action, + R.string.cancel_action, + new Runnable() { + @Override + public void run() { + if (mSelectedContextAccount instanceof Account) { + Account realAccount = (Account) mSelectedContextAccount; + mHandler.workingAccount(realAccount, + R.string.recreating_account); + MessagingController.getInstance(getApplication()) + .recreate(realAccount, null); + } + } + }); + } + case DIALOG_NO_FILE_MANAGER: { + return ConfirmationDialog.create(this, id, + R.string.import_dialog_error_title, + getString(R.string.import_dialog_error_message), + R.string.open_market, + R.string.close, + new Runnable() { + @Override + public void run() { + Uri uri = Uri.parse(ANDROID_MARKET_URL); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + } + }); + } } + return super.onCreateDialog(id); } @Override public void onPrepareDialog(int id, Dialog d) { - AlertDialog alert = (AlertDialog) d; switch (id) { - case DIALOG_REMOVE_ACCOUNT: - alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt, - mSelectedContextAccount.getDescription())); - break; - case DIALOG_CLEAR_ACCOUNT: - alert.setMessage(getString(R.string.account_clear_dlg_instructions_fmt, - mSelectedContextAccount.getDescription())); - break; - case DIALOG_RECREATE_ACCOUNT: - alert.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt, - mSelectedContextAccount.getDescription())); - break; - case DIALOG_NO_FILE_MANAGER: - alert.setMessage(getString(R.string.import_dialog_error_message)); - break; + case DIALOG_REMOVE_ACCOUNT: { + alert.setMessage(getString(R.string.account_delete_dlg_instructions_fmt, + mSelectedContextAccount.getDescription())); + break; + } + case DIALOG_CLEAR_ACCOUNT: { + alert.setMessage(getString(R.string.account_clear_dlg_instructions_fmt, + mSelectedContextAccount.getDescription())); + break; + } + case DIALOG_RECREATE_ACCOUNT: { + alert.setMessage(getString(R.string.account_recreate_dlg_instructions_fmt, + mSelectedContextAccount.getDescription())); + break; + } } super.onPrepareDialog(id, d); From 482ae352c158d75eb3db820305d020b809715a32 Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 04:51:29 +0100 Subject: [PATCH 44/80] Reverted most of the recent message header changes Clicking the additional headers area still allows to hide that view again. --- res/layout/message_view_header.xml | 147 ++++++++++++------------ src/com/fsck/k9/view/MessageHeader.java | 21 ++-- 2 files changed, 86 insertions(+), 82 deletions(-) diff --git a/res/layout/message_view_header.xml b/res/layout/message_view_header.xml index 19bd2a52d..67f8fe010 100644 --- a/res/layout/message_view_header.xml +++ b/res/layout/message_view_header.xml @@ -17,7 +17,7 @@ android:layout_height="wrap_content" android:stretchColumns="1" android:shrinkColumns="1" - android:background="@drawable/message_view_header_background"> + android:background="@color/message_view_header_background"> @@ -30,7 +30,6 @@ @@ -123,68 +122,16 @@ - - - - - - - - - - - - - - - - - - + @@ -231,32 +178,82 @@ android:textColor="?android:attr/textColorPrimary" android:textAppearance="?android:attr/textAppearanceSmall"/> - - - - - + + + + + + + + + + - + + + android:layout_height="21.5dp" + android:focusable="true" + android:clickable="true" + android:background="@drawable/message_view_header_background"> + + + + + + + + + + + + + + Date: Sun, 18 Mar 2012 18:28:03 +0100 Subject: [PATCH 45/80] Fix for Issue 4164: Force close when moving an email to another folder --- src/com/fsck/k9/activity/ChooseFolder.java | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index dc4a88115..2525c5763 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -1,6 +1,11 @@ package com.fsck.k9.activity; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; @@ -19,15 +24,16 @@ import android.widget.EditText; import android.widget.Filter; import android.widget.ListView; import android.widget.TextView; -import com.fsck.k9.*; + +import com.fsck.k9.Account; import com.fsck.k9.Account.FolderMode; +import com.fsck.k9.K9; +import com.fsck.k9.Preferences; +import com.fsck.k9.R; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.MessagingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; public class ChooseFolder extends K9ListActivity { String mFolder; @@ -40,6 +46,8 @@ public class ChooseFolder extends K9ListActivity { boolean hideCurrentFolder = true; boolean showOptionNone = false; boolean showDisplayableOnly = false; + + private List folderList; /** * What folders to display.
@@ -361,17 +369,19 @@ public class ChooseFolder extends K9ListActivity { return aName.compareToIgnoreCase(bName); } }); - mAdapter.setNotifyOnChange(false); int selectedFolder = -1; + // We're not allowed to change the adapter from a background thread, so we use + // a java.util.List to build a list of the folder names. + // We'll add the folder names to the adapter from the UI-thread (see the 'finally' block). + folderList = new ArrayList(); try { - mAdapter.clear(); int position = 0; for (String name : localFolders) { if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) { - mAdapter.add(getString(R.string.special_mailbox_name_inbox)); + folderList.add(getString(R.string.special_mailbox_name_inbox)); heldInbox = name; } else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) { - mAdapter.add(name); + folderList.add(name); } if (mSelectFolder != null) { @@ -390,11 +400,13 @@ public class ChooseFolder extends K9ListActivity { position++; } } finally { - mAdapter.setNotifyOnChange(true); runOnUiThread(new Runnable() { public void run() { - // runOnUiThread( - mAdapter.notifyDataSetChanged(); + // Now we're in the UI-thread, we can safely change the contents of the adapter. + mAdapter.clear(); + for (String folderName: folderList) { + mAdapter.add(folderName); + } } }); } From 6bdaac4353cfcb7bc61674cefd6b2fff5741016b Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 21:46:32 +0100 Subject: [PATCH 46/80] Fixed whitespace (tabs vs. spaces) --- src/com/fsck/k9/activity/ChooseFolder.java | 88 +++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 2525c5763..98210bc7c 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -46,7 +46,7 @@ public class ChooseFolder extends K9ListActivity { boolean hideCurrentFolder = true; boolean showOptionNone = false; boolean showDisplayableOnly = false; - + private List folderList; /** @@ -216,7 +216,7 @@ public class ChooseFolder extends K9ListActivity { return true; } case R.id.filter_folders: { - onEnterFilter(); + onEnterFilter(); } return true; default: @@ -236,43 +236,43 @@ public class ChooseFolder extends K9ListActivity { * Filter {@link #mAdapter} with the user-input. */ private void onEnterFilter() { - final AlertDialog.Builder filterAlert = new AlertDialog.Builder(this); + final AlertDialog.Builder filterAlert = new AlertDialog.Builder(this); - final EditText input = new EditText(this); - input.addTextChangedListener(new TextWatcher() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - mAdapter.getFilter().filter(input.getText().toString()); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } - - @Override - public void afterTextChanged(Editable s) { - } - }); - input.setHint(R.string.folder_list_filter_hint); - filterAlert.setView(input); + final EditText input = new EditText(this); + input.addTextChangedListener(new TextWatcher() { - filterAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - String value = input.getText().toString().trim(); - mAdapter.getFilter().filter(value); - } - }); + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + mAdapter.getFilter().filter(input.getText().toString()); + } - filterAlert.setNegativeButton("Cancel", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - mAdapter.getFilter().filter(""); - } - }); + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } - filterAlert.show(); + @Override + public void afterTextChanged(Editable s) { + } + }); + input.setHint(R.string.folder_list_filter_hint); + filterAlert.setView(input); + + filterAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String value = input.getText().toString().trim(); + mAdapter.getFilter().filter(value); + } + }); + + filterAlert.setNegativeButton("Cancel", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mAdapter.getFilter().filter(""); + } + }); + + filterAlert.show(); } @@ -370,18 +370,18 @@ public class ChooseFolder extends K9ListActivity { } }); int selectedFolder = -1; - // We're not allowed to change the adapter from a background thread, so we use + // We're not allowed to change the adapter from a background thread, so we use // a java.util.List to build a list of the folder names. - // We'll add the folder names to the adapter from the UI-thread (see the 'finally' block). + // We'll add the folder names to the adapter from the UI-thread (see the 'finally' block). folderList = new ArrayList(); try { int position = 0; for (String name : localFolders) { if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) { - folderList.add(getString(R.string.special_mailbox_name_inbox)); + folderList.add(getString(R.string.special_mailbox_name_inbox)); heldInbox = name; } else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) { - folderList.add(name); + folderList.add(name); } if (mSelectFolder != null) { @@ -402,11 +402,11 @@ public class ChooseFolder extends K9ListActivity { } finally { runOnUiThread(new Runnable() { public void run() { - // Now we're in the UI-thread, we can safely change the contents of the adapter. - mAdapter.clear(); - for (String folderName: folderList) { - mAdapter.add(folderName); - } + // Now we're in the UI-thread, we can safely change the contents of the adapter. + mAdapter.clear(); + for (String folderName: folderList) { + mAdapter.add(folderName); + } } }); } From 09bc07596ec7bff27ae9a912a86fc5804277fc7d Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 21:48:22 +0100 Subject: [PATCH 47/80] Got rid of unnecessary instance variable --- src/com/fsck/k9/activity/ChooseFolder.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 98210bc7c..7e7a9e2a4 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -47,8 +47,6 @@ public class ChooseFolder extends K9ListActivity { boolean showOptionNone = false; boolean showDisplayableOnly = false; - private List folderList; - /** * What folders to display.
* Initialized to whatever is configured @@ -370,10 +368,12 @@ public class ChooseFolder extends K9ListActivity { } }); int selectedFolder = -1; - // We're not allowed to change the adapter from a background thread, so we use - // a java.util.List to build a list of the folder names. - // We'll add the folder names to the adapter from the UI-thread (see the 'finally' block). - folderList = new ArrayList(); + + /* + * We're not allowed to change the adapter from a background thread, so we collect the + * folder names and update the adapter in the UI thread (see finally block). + */ + final List folderList = new ArrayList(); try { int position = 0; for (String name : localFolders) { From b7c1f8ab2fb1a2be01ffa783e7491417a7bfd9c5 Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 21:51:01 +0100 Subject: [PATCH 48/80] Prefixed instance variables with "m" to comply with K-9 code style --- src/com/fsck/k9/activity/ChooseFolder.java | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 7e7a9e2a4..746d2a17f 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -42,10 +42,10 @@ public class ChooseFolder extends K9ListActivity { MessageReference mMessageReference; ArrayAdapter mAdapter; private ChooseFolderHandler mHandler = new ChooseFolderHandler(); - String heldInbox = null; - boolean hideCurrentFolder = true; - boolean showOptionNone = false; - boolean showDisplayableOnly = false; + String mHeldInbox = null; + boolean mHideCurrentFolder = true; + boolean mShowOptionNone = false; + boolean mShowDisplayableOnly = false; /** * What folders to display.
@@ -59,7 +59,7 @@ public class ChooseFolder extends K9ListActivity { * Created on the fly and invalidated if a new * set of folders is chosen via {@link #onOptionsItemSelected(MenuItem)} */ - private FolderListFilter myFilter = null; + private FolderListFilter mMyFilter = null; public static final String EXTRA_ACCOUNT = "com.fsck.k9.ChooseFolder_account"; public static final String EXTRA_CUR_FOLDER = "com.fsck.k9.ChooseFolder_curfolder"; @@ -85,13 +85,13 @@ public class ChooseFolder extends K9ListActivity { mFolder = intent.getStringExtra(EXTRA_CUR_FOLDER); mSelectFolder = intent.getStringExtra(EXTRA_SEL_FOLDER); if (intent.getStringExtra(EXTRA_SHOW_CURRENT) != null) { - hideCurrentFolder = false; + mHideCurrentFolder = false; } if (intent.getStringExtra(EXTRA_SHOW_FOLDER_NONE) != null) { - showOptionNone = true; + mShowOptionNone = true; } if (intent.getStringExtra(EXTRA_SHOW_DISPLAYABLE_ONLY) != null) { - showDisplayableOnly = true; + mShowDisplayableOnly = true; } if (mFolder == null) mFolder = ""; @@ -121,8 +121,8 @@ public class ChooseFolder extends K9ListActivity { intent.putExtra(EXTRA_ACCOUNT, mAccount.getUuid()); intent.putExtra(EXTRA_CUR_FOLDER, mFolder); String destFolderName = (String)((TextView)view).getText(); - if (heldInbox != null && getString(R.string.special_mailbox_name_inbox).equals(destFolderName)) { - destFolderName = heldInbox; + if (mHeldInbox != null && getString(R.string.special_mailbox_name_inbox).equals(destFolderName)) { + destFolderName = mHeldInbox; } intent.putExtra(EXTRA_NEW_FOLDER, destFolderName); intent.putExtra(EXTRA_MESSAGE, mMessageReference); @@ -277,8 +277,8 @@ public class ChooseFolder extends K9ListActivity { private void setDisplayMode(FolderMode aMode) { mMode = aMode; // invalidate the current filter as it is working on an inval - if (myFilter != null) { - myFilter.invalidate(); + if (mMyFilter != null) { + mMyFilter.invalidate(); } //re-populate the list MessagingController.getInstance(getApplication()).listFolders(mAccount, @@ -322,7 +322,7 @@ public class ChooseFolder extends K9ListActivity { String name = folder.getName(); // Inbox needs to be compared case-insensitively - if (hideCurrentFolder && (name.equals(mFolder) || + if (mHideCurrentFolder && (name.equals(mFolder) || (mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name)))) { continue; } @@ -345,7 +345,7 @@ public class ChooseFolder extends K9ListActivity { } - if (showOptionNone) { + if (mShowOptionNone) { localFolders.add(K9.FOLDER_NONE); } @@ -379,7 +379,7 @@ public class ChooseFolder extends K9ListActivity { for (String name : localFolders) { if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) { folderList.add(getString(R.string.special_mailbox_name_inbox)); - heldInbox = name; + mHeldInbox = name; } else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) { folderList.add(name); } From 4f2412eacda3daae3181bbaaede6eaaaf4e200b2 Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 22:06:44 +0100 Subject: [PATCH 49/80] Changed code formatting. No functional changes --- src/com/fsck/k9/activity/ChooseFolder.java | 182 +++++++++++---------- 1 file changed, 92 insertions(+), 90 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 746d2a17f..170f64358 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -36,6 +36,16 @@ import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.MessagingException; public class ChooseFolder extends K9ListActivity { + public static final String EXTRA_ACCOUNT = "com.fsck.k9.ChooseFolder_account"; + public static final String EXTRA_CUR_FOLDER = "com.fsck.k9.ChooseFolder_curfolder"; + public static final String EXTRA_SEL_FOLDER = "com.fsck.k9.ChooseFolder_selfolder"; + public static final String EXTRA_NEW_FOLDER = "com.fsck.k9.ChooseFolder_newfolder"; + public static final String EXTRA_MESSAGE = "com.fsck.k9.ChooseFolder_message"; + public static final String EXTRA_SHOW_CURRENT = "com.fsck.k9.ChooseFolder_showcurrent"; + public static final String EXTRA_SHOW_FOLDER_NONE = "com.fsck.k9.ChooseFolder_showOptionNone"; + public static final String EXTRA_SHOW_DISPLAYABLE_ONLY = "com.fsck.k9.ChooseFolder_showDisplayableOnly"; + + String mFolder; String mSelectFolder; Account mAccount; @@ -54,6 +64,7 @@ public class ChooseFolder extends K9ListActivity { * while this activity is showing. */ private Account.FolderMode mMode; + /** * Current filter used by our ArrayAdapter.
* Created on the fly and invalidated if a new @@ -61,14 +72,6 @@ public class ChooseFolder extends K9ListActivity { */ private FolderListFilter mMyFilter = null; - public static final String EXTRA_ACCOUNT = "com.fsck.k9.ChooseFolder_account"; - public static final String EXTRA_CUR_FOLDER = "com.fsck.k9.ChooseFolder_curfolder"; - public static final String EXTRA_SEL_FOLDER = "com.fsck.k9.ChooseFolder_selfolder"; - public static final String EXTRA_NEW_FOLDER = "com.fsck.k9.ChooseFolder_newfolder"; - public static final String EXTRA_MESSAGE = "com.fsck.k9.ChooseFolder_message"; - public static final String EXTRA_SHOW_CURRENT = "com.fsck.k9.ChooseFolder_showcurrent"; - public static final String EXTRA_SHOW_FOLDER_NONE = "com.fsck.k9.ChooseFolder_showOptionNone"; - public static final String EXTRA_SHOW_DISPLAYABLE_ONLY = "com.fsck.k9.ChooseFolder_showDisplayableOnly"; @Override public void onCreate(Bundle savedInstanceState) { @@ -110,55 +113,54 @@ public class ChooseFolder extends K9ListActivity { setListAdapter(mAdapter); - mMode = mAccount.getFolderTargetMode(); MessagingController.getInstance(getApplication()).listFolders(mAccount, false, mListener); - this.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ACCOUNT, mAccount.getUuid()); - intent.putExtra(EXTRA_CUR_FOLDER, mFolder); + Intent result = new Intent(); + result.putExtra(EXTRA_ACCOUNT, mAccount.getUuid()); + result.putExtra(EXTRA_CUR_FOLDER, mFolder); String destFolderName = (String)((TextView)view).getText(); if (mHeldInbox != null && getString(R.string.special_mailbox_name_inbox).equals(destFolderName)) { destFolderName = mHeldInbox; } - intent.putExtra(EXTRA_NEW_FOLDER, destFolderName); - intent.putExtra(EXTRA_MESSAGE, mMessageReference); - setResult(RESULT_OK, intent); + result.putExtra(EXTRA_NEW_FOLDER, destFolderName); + result.putExtra(EXTRA_MESSAGE, mMessageReference); + setResult(RESULT_OK, result); finish(); } }); - } class ChooseFolderHandler extends Handler { - private static final int MSG_PROGRESS = 2; - private static final int MSG_DATA_CHANGED = 3; private static final int MSG_SET_SELECTED_FOLDER = 4; @Override public void handleMessage(android.os.Message msg) { switch (msg.what) { - case MSG_PROGRESS: - setProgressBarIndeterminateVisibility(msg.arg1 != 0); - break; - case MSG_DATA_CHANGED: - mAdapter.notifyDataSetChanged(); + case MSG_PROGRESS: { + setProgressBarIndeterminateVisibility(msg.arg1 != 0); + break; + } + case MSG_DATA_CHANGED: { + mAdapter.notifyDataSetChanged(); - /* - * Only enable the text filter after the list has been - * populated to avoid possible race conditions because our - * FolderListFilter isn't really thread-safe. - */ - getListView().setTextFilterEnabled(true); - break; - case MSG_SET_SELECTED_FOLDER: - getListView().setSelection(msg.arg1); - break; + /* + * Only enable the text filter after the list has been + * populated to avoid possible race conditions because our + * FolderListFilter isn't really thread-safe. + */ + getListView().setTextFilterEnabled(true); + break; + } + case MSG_SET_SELECTED_FOLDER: { + getListView().setSelection(msg.arg1); + break; + } } } @@ -181,52 +183,48 @@ public class ChooseFolder extends K9ListActivity { } } - @Override public boolean onCreateOptionsMenu(Menu menu) { + @Override + public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.folder_select_option, menu); return true; } - @Override public boolean onOptionsItemSelected(MenuItem item) { + @Override + public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - - - case R.id.display_1st_class: { - setDisplayMode(FolderMode.FIRST_CLASS); - return true; - } - case R.id.display_1st_and_2nd_class: { - setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS); - return true; - } - case R.id.display_not_second_class: { - setDisplayMode(FolderMode.NOT_SECOND_CLASS); - return true; - } - case R.id.display_all: { - setDisplayMode(FolderMode.ALL); - return true; - } - - case R.id.list_folders: { - onRefresh(); - - return true; - } - case R.id.filter_folders: { - onEnterFilter(); - } - return true; - default: - return super.onOptionsItemSelected(item); + case R.id.display_1st_class: { + setDisplayMode(FolderMode.FIRST_CLASS); + return true; + } + case R.id.display_1st_and_2nd_class: { + setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS); + return true; + } + case R.id.display_not_second_class: { + setDisplayMode(FolderMode.NOT_SECOND_CLASS); + return true; + } + case R.id.display_all: { + setDisplayMode(FolderMode.ALL); + return true; + } + case R.id.list_folders: { + onRefresh(); + return true; + } + case R.id.filter_folders: { + onEnterFilter(); + return true; + } + default: { + return super.onOptionsItemSelected(item); + } } } - private void onRefresh() { - MessagingController.getInstance(getApplication()).listFolders(mAccount, true, mListener); - } /** @@ -238,40 +236,37 @@ public class ChooseFolder extends K9ListActivity { final EditText input = new EditText(this); input.addTextChangedListener(new TextWatcher() { - @Override public void onTextChanged(CharSequence s, int start, int before, int count) { mAdapter.getFilter().filter(input.getText().toString()); } @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) { - } + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + /* not used */ } @Override - public void afterTextChanged(Editable s) { - } + public void afterTextChanged(Editable s) { /* not used */ } }); input.setHint(R.string.folder_list_filter_hint); filterAlert.setView(input); filterAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString().trim(); mAdapter.getFilter().filter(value); } }); - filterAlert.setNegativeButton("Cancel", - new DialogInterface.OnClickListener() { + filterAlert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int whichButton) { mAdapter.getFilter().filter(""); } }); filterAlert.show(); - } private void setDisplayMode(FolderMode aMode) { @@ -281,8 +276,7 @@ public class ChooseFolder extends K9ListActivity { mMyFilter.invalidate(); } //re-populate the list - MessagingController.getInstance(getApplication()).listFolders(mAccount, - false, mListener); + MessagingController.getInstance(getApplication()).listFolders(mAccount, false, mListener); } private MessagingListener mListener = new MessagingListener() { @@ -322,23 +316,27 @@ public class ChooseFolder extends K9ListActivity { String name = folder.getName(); // Inbox needs to be compared case-insensitively - if (mHideCurrentFolder && (name.equals(mFolder) || - (mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name)))) { + if (mHideCurrentFolder && (name.equals(mFolder) || ( + mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && + mAccount.getInboxFolderName().equalsIgnoreCase(name)))) { continue; } try { folder.refresh(prefs); Folder.FolderClass fMode = folder.getDisplayClass(); - if ((aMode == Account.FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS) - || (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS && + if ((aMode == Account.FolderMode.FIRST_CLASS && + fMode != Folder.FolderClass.FIRST_CLASS) || ( + aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS && fMode != Folder.FolderClass.FIRST_CLASS && - fMode != Folder.FolderClass.SECOND_CLASS) - || (aMode == Account.FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) { + fMode != Folder.FolderClass.SECOND_CLASS) || ( + aMode == Account.FolderMode.NOT_SECOND_CLASS && + fMode == Folder.FolderClass.SECOND_CLASS)) { continue; } } catch (MessagingException me) { - Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + folder.getName(), me); + Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + + folder.getName(), me); } localFolders.add(folder.getName()); @@ -350,6 +348,7 @@ public class ChooseFolder extends K9ListActivity { } Collections.sort(localFolders, new Comparator() { + @Override public int compare(String aName, String bName) { if (K9.FOLDER_NONE.equalsIgnoreCase(aName)) { return -1; @@ -380,7 +379,8 @@ public class ChooseFolder extends K9ListActivity { if (mAccount.getInboxFolderName().equalsIgnoreCase(name)) { folderList.add(getString(R.string.special_mailbox_name_inbox)); mHeldInbox = name; - } else if (!K9.ERROR_FOLDER_NAME.equals(name) && !account.getOutboxFolderName().equals(name)) { + } else if (!K9.ERROR_FOLDER_NAME.equals(name) && + !account.getOutboxFolderName().equals(name)) { folderList.add(name); } @@ -393,14 +393,16 @@ public class ChooseFolder extends K9ListActivity { if (name.equals(mSelectFolder)) { selectedFolder = position; } - } else if (name.equals(mFolder) || - (mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && mAccount.getInboxFolderName().equalsIgnoreCase(name))) { + } else if (name.equals(mFolder) || ( + mAccount.getInboxFolderName().equalsIgnoreCase(mFolder) && + mAccount.getInboxFolderName().equalsIgnoreCase(name))) { selectedFolder = position; } position++; } } finally { runOnUiThread(new Runnable() { + @Override public void run() { // Now we're in the UI-thread, we can safely change the contents of the adapter. mAdapter.clear(); From 16afff4dfcb6adcedb021d6753feb660a1540e5c Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 22:09:52 +0100 Subject: [PATCH 50/80] Notify the adapter from runOnUiThread() instead of using Handler --- src/com/fsck/k9/activity/ChooseFolder.java | 31 ++++++++-------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 170f64358..1cfdfa976 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -135,9 +135,8 @@ public class ChooseFolder extends K9ListActivity { } class ChooseFolderHandler extends Handler { - private static final int MSG_PROGRESS = 2; - private static final int MSG_DATA_CHANGED = 3; - private static final int MSG_SET_SELECTED_FOLDER = 4; + private static final int MSG_PROGRESS = 1; + private static final int MSG_SET_SELECTED_FOLDER = 2; @Override public void handleMessage(android.os.Message msg) { @@ -146,17 +145,6 @@ public class ChooseFolder extends K9ListActivity { setProgressBarIndeterminateVisibility(msg.arg1 != 0); break; } - case MSG_DATA_CHANGED: { - mAdapter.notifyDataSetChanged(); - - /* - * Only enable the text filter after the list has been - * populated to avoid possible race conditions because our - * FolderListFilter isn't really thread-safe. - */ - getListView().setTextFilterEnabled(true); - break; - } case MSG_SET_SELECTED_FOLDER: { getListView().setSelection(msg.arg1); break; @@ -177,10 +165,6 @@ public class ChooseFolder extends K9ListActivity { msg.arg1 = position; sendMessage(msg); } - - public void dataChanged() { - sendEmptyMessage(MSG_DATA_CHANGED); - } } @Override @@ -409,12 +393,19 @@ public class ChooseFolder extends K9ListActivity { for (String folderName: folderList) { mAdapter.add(folderName); } + + mAdapter.notifyDataSetChanged(); + + /* + * Only enable the text filter after the list has been + * populated to avoid possible race conditions because our + * FolderListFilter isn't really thread-safe. + */ + getListView().setTextFilterEnabled(true); } }); } - mHandler.dataChanged(); - if (selectedFolder != -1) { mHandler.setSelectedFolder(selectedFolder); } From 8c66a2f835b76f323682862a6f4bc4619a86907c Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 22:13:27 +0100 Subject: [PATCH 51/80] Replaced hardcoded strings with references to string resources --- src/com/fsck/k9/activity/ChooseFolder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 1cfdfa976..a5b8568fe 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -235,7 +235,8 @@ public class ChooseFolder extends K9ListActivity { input.setHint(R.string.folder_list_filter_hint); filterAlert.setView(input); - filterAlert.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + String okay = getString(R.string.okay_action); + filterAlert.setPositiveButton(okay, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { String value = input.getText().toString().trim(); @@ -243,7 +244,8 @@ public class ChooseFolder extends K9ListActivity { } }); - filterAlert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + String cancel = getString(R.string.cancel_action); + filterAlert.setNegativeButton(cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { mAdapter.getFilter().filter(""); From 5245191900f4568a068a1f35160d3d122c325886 Mon Sep 17 00:00:00 2001 From: cketti Date: Sun, 18 Mar 2012 22:43:47 +0100 Subject: [PATCH 52/80] Notify the listener provided as argument to doRefreshRemote() With this change pressing "Refresh folders" in ChooseFolder actually refreshes the folder list. --- src/com/fsck/k9/controller/MessagingController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 33acd96f1..290679e86 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -449,7 +449,7 @@ public class MessagingController implements Runnable { } } - private void doRefreshRemote(final Account account, MessagingListener listener) { + private void doRefreshRemote(final Account account, final MessagingListener listener) { put("doRefreshRemote", listener, new Runnable() { @Override public void run() { @@ -492,14 +492,14 @@ public class MessagingController implements Runnable { localFolders = localStore.getPersonalNamespaces(false); Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY); - for (MessagingListener l : getListeners()) { + for (MessagingListener l : getListeners(listener)) { l.listFolders(account, folderArray); } - for (MessagingListener l : getListeners()) { + for (MessagingListener l : getListeners(listener)) { l.listFoldersFinished(account); } } catch (Exception e) { - for (MessagingListener l : getListeners()) { + for (MessagingListener l : getListeners(listener)) { l.listFoldersFailed(account, ""); } addErrorMessage(account, null, e); From 1d28eb003df376a9ba186fece011d499ef1da96a Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 19 Mar 2012 04:44:41 +0100 Subject: [PATCH 53/80] Added a setting to disable marking messages as read on viewing --- res/values/strings.xml | 2 ++ res/xml/account_settings_preferences.xml | 11 +++++++++++ src/com/fsck/k9/Account.java | 13 +++++++++++++ src/com/fsck/k9/activity/MessageView.java | 13 +++++++++---- src/com/fsck/k9/activity/setup/AccountSettings.java | 11 ++++++----- src/com/fsck/k9/controller/MessagingController.java | 2 +- src/com/fsck/k9/preferences/AccountSettings.java | 3 +++ src/com/fsck/k9/preferences/Settings.java | 2 +- 8 files changed, 46 insertions(+), 11 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 820960b66..8d66d64ea 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -549,6 +549,8 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Searches for unread messages when Notification is opened Show unread count Show the number of unread messages in the notification bar. + Mark message as read when opening + Mark a message as read when it is opened for viewing Enable refile buttons Show the Archive, Move, and Spam buttons. diff --git a/res/xml/account_settings_preferences.xml b/res/xml/account_settings_preferences.xml index 9fa5a11a6..683827258 100644 --- a/res/xml/account_settings_preferences.xml +++ b/res/xml/account_settings_preferences.xml @@ -81,6 +81,17 @@ + + + + + + diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index c04e89c06..98c3ddafb 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -149,6 +149,7 @@ public class Account implements BaseAccount { private String mCryptoApp; private boolean mCryptoAutoSignature; private boolean mCryptoAutoEncrypt; + private boolean mMarkMessageAsReadOnView; private CryptoProvider mCryptoProvider = null; @@ -236,6 +237,7 @@ public class Account implements BaseAccount { mCryptoAutoSignature = false; mCryptoAutoEncrypt = false; mEnabled = true; + mMarkMessageAsReadOnView = true; searchableFolders = Searchable.ALL; @@ -391,6 +393,7 @@ public class Account implements BaseAccount { mCryptoAutoSignature = prefs.getBoolean(mUuid + ".cryptoAutoSignature", false); mCryptoAutoEncrypt = prefs.getBoolean(mUuid + ".cryptoAutoEncrypt", false); mEnabled = prefs.getBoolean(mUuid + ".enabled", true); + mMarkMessageAsReadOnView = prefs.getBoolean(mUuid + ".markMessageAsReadOnView", true); } protected synchronized void delete(Preferences preferences) { @@ -472,6 +475,7 @@ public class Account implements BaseAccount { editor.remove(mUuid + ".enabled"); editor.remove(mUuid + ".enableMoveButtons"); editor.remove(mUuid + ".hideMoveButtonsEnum"); + editor.remove(mUuid + ".markMessageAsReadOnView"); for (String type : networkTypes) { editor.remove(mUuid + ".useCompression." + type); } @@ -632,6 +636,7 @@ public class Account implements BaseAccount { editor.putBoolean(mUuid + ".cryptoAutoSignature", mCryptoAutoSignature); editor.putBoolean(mUuid + ".cryptoAutoEncrypt", mCryptoAutoEncrypt); editor.putBoolean(mUuid + ".enabled", mEnabled); + editor.putBoolean(mUuid + ".markMessageAsReadOnView", mMarkMessageAsReadOnView); editor.putBoolean(mUuid + ".vibrate", mNotificationSetting.shouldVibrate()); editor.putInt(mUuid + ".vibratePattern", mNotificationSetting.getVibratePattern()); @@ -1500,4 +1505,12 @@ public class Account implements BaseAccount { public synchronized void setEnabled(boolean enabled) { mEnabled = enabled; } + + public synchronized boolean isMarkMessageAsReadOnView() { + return mMarkMessageAsReadOnView; + } + + public synchronized void setMarkMessageAsReadOnView(boolean value) { + mMarkMessageAsReadOnView = value; + } } diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java index 1ffb9586e..87d968681 100644 --- a/src/com/fsck/k9/activity/MessageView.java +++ b/src/com/fsck/k9/activity/MessageView.java @@ -756,17 +756,16 @@ public class MessageView extends K9Activity implements OnClickListener { mPrevious.requestFocus(); } - private void onMarkAsUnread() { + private void onToggleRead() { if (mMessage != null) { mController.setFlag(mAccount, mMessage.getFolder().getName(), - new Message[] { mMessage }, Flag.SEEN, false); + new Message[] { mMessage }, Flag.SEEN, !mMessage.isSet(Flag.SEEN)); mMessageView.setHeaders(mMessage, mAccount); String subject = mMessage.getSubject(); setTitle(subject); } } - private void onDownloadRemainder() { if (mMessage.isSet(Flag.X_DOWNLOADED_FULL)) { return; @@ -833,7 +832,7 @@ public class MessageView extends K9Activity implements OnClickListener { onSendAlternate(); break; case R.id.mark_as_unread: - onMarkAsUnread(); + onToggleRead(); break; case R.id.flag: onFlag(); @@ -944,6 +943,12 @@ public class MessageView extends K9Activity implements OnClickListener { additionalHeadersItem.setTitle(mMessageView.additionalHeadersVisible() ? R.string.hide_full_header_action : R.string.show_full_header_action); } + + if (mMessage != null) { + int actionTitle = mMessage.isSet(Flag.SEEN) ? + R.string.mark_as_unread_action : R.string.mark_as_read_action; + menu.findItem(R.id.mark_as_unread).setTitle(actionTitle); + } } return super.onPrepareOptionsMenu(menu); } diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index 9ac8f7058..f38cfa20f 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -48,6 +48,7 @@ public class AccountSettings extends K9PreferenceActivity { private static final String PREFERENCE_SCREEN_PUSH_ADVANCED = "push_advanced"; private static final String PREFERENCE_DESCRIPTION = "account_description"; + private static final String PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW = "mark_message_as_read_on_view"; private static final String PREFERENCE_COMPOSITION = "composition"; private static final String PREFERENCE_MANAGE_IDENTITIES = "manage_identities"; private static final String PREFERENCE_FREQUENCY = "account_check_frequency"; @@ -94,9 +95,7 @@ public class AccountSettings extends K9PreferenceActivity { private static final String PREFERENCE_CRYPTO_APP = "crypto_app"; private static final String PREFERENCE_CRYPTO_AUTO_SIGNATURE = "crypto_auto_signature"; private static final String PREFERENCE_CRYPTO_AUTO_ENCRYPT = "crypto_auto_encrypt"; - private static final String PREFERENCE_LOCAL_STORAGE_PROVIDER = "local_storage_provider"; - private static final String PREFERENCE_CATEGORY_FOLDERS = "folders"; private static final String PREFERENCE_ARCHIVE_FOLDER = "archive_folder"; private static final String PREFERENCE_DRAFTS_FOLDER = "drafts_folder"; @@ -114,6 +113,7 @@ public class AccountSettings extends K9PreferenceActivity { private PreferenceScreen mComposingScreen; private EditTextPreference mAccountDescription; + private CheckBoxPreference mMarkMessageAsReadOnView; private ListPreference mCheckFrequency; private ListPreference mDisplayCount; private ListPreference mMessageAge; @@ -157,10 +157,7 @@ public class AccountSettings extends K9PreferenceActivity { private ListPreference mCryptoApp; private CheckBoxPreference mCryptoAutoSignature; private CheckBoxPreference mCryptoAutoEncrypt; - private ListPreference mLocalStorageProvider; - - private ListPreference mArchiveFolder; private ListPreference mDraftsFolder; private ListPreference mSentFolder; @@ -204,6 +201,9 @@ public class AccountSettings extends K9PreferenceActivity { } }); + mMarkMessageAsReadOnView = (CheckBoxPreference) findPreference(PREFERENCE_MARK_MESSAGE_AS_READ_ON_VIEW); + mMarkMessageAsReadOnView.setChecked(mAccount.isMarkMessageAsReadOnView()); + mMessageFormat = (ListPreference) findPreference(PREFERENCE_MESSAGE_FORMAT); mMessageFormat.setValue(mAccount.getMessageFormat().name()); mMessageFormat.setSummary(mMessageFormat.getEntry()); @@ -672,6 +672,7 @@ public class AccountSettings extends K9PreferenceActivity { } mAccount.setDescription(mAccountDescription.getText()); + mAccount.setMarkMessageAsReadOnView(mMarkMessageAsReadOnView.isChecked()); mAccount.setNotifyNewMail(mAccountNotify.isChecked()); mAccount.setNotifySelfNewMail(mAccountNotifySelf.isChecked()); mAccount.setShowOngoing(mAccountNotifySync.isChecked()); diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 290679e86..9a7065cbd 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2846,7 +2846,7 @@ public class MessagingController implements Runnable { || message.getId() == 0) { throw new IllegalArgumentException("Message not found: folder=" + folder + ", uid=" + uid); } - if (!message.isSet(Flag.SEEN)) { + if (account.isMarkMessageAsReadOnView() && !message.isSet(Flag.SEEN)) { message.setFlag(Flag.SEEN, true); setFlag(new Message[] { message }, Flag.SEEN, true); } diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index fdc311820..f2dcf3669 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -96,6 +96,9 @@ public class AccountSettings { s.put("localStorageProvider", Settings.versions( new V(1, new StorageProviderSetting()) )); + s.put("markMessageAsReadOnView", Settings.versions( + new V(7, new BooleanSetting(true)) + )); s.put("maxPushFolders", Settings.versions( new V(1, new IntegerRangeSetting(0, 100, 10)) )); diff --git a/src/com/fsck/k9/preferences/Settings.java b/src/com/fsck/k9/preferences/Settings.java index 3b2bbb979..637d6d74d 100644 --- a/src/com/fsck/k9/preferences/Settings.java +++ b/src/com/fsck/k9/preferences/Settings.java @@ -35,7 +35,7 @@ public class Settings { * * @see SettingsExporter */ - public static final int VERSION = 6; + public static final int VERSION = 7; public static Map validate(int version, Map> settings, From 8180fd9ad2113c5c92a9ea7de2d19b396ffa10f8 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 22 Mar 2012 22:17:10 +0100 Subject: [PATCH 54/80] Added a button to switch the identity in MessageCompose Originally I wanted to use a Spinner, but it doesn't support multiple view types (see [1]). Those are necessary because we use different layouts for accounts (section headers) and identities (selectable list items). Removed the ChooseAccount activity because it's now unused. --- res/layout/choose_account.xml | 9 - res/layout/choose_account_item.xml | 42 +-- res/layout/choose_identity_item.xml | 17 +- res/layout/message_compose.xml | 33 ++- res/menu/message_compose_option.xml | 6 - src/com/fsck/k9/activity/ChooseAccount.java | 243 ------------------ .../k9/activity/K9ExpandableListActivity.java | 18 -- src/com/fsck/k9/activity/MessageCompose.java | 227 ++++++++++++---- 8 files changed, 215 insertions(+), 380 deletions(-) delete mode 100644 res/layout/choose_account.xml delete mode 100644 src/com/fsck/k9/activity/ChooseAccount.java delete mode 100644 src/com/fsck/k9/activity/K9ExpandableListActivity.java diff --git a/res/layout/choose_account.xml b/res/layout/choose_account.xml deleted file mode 100644 index 09c8fe2d8..000000000 --- a/res/layout/choose_account.xml +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/res/layout/choose_account_item.xml b/res/layout/choose_account_item.xml index d212594f0..361db3d61 100644 --- a/res/layout/choose_account_item.xml +++ b/res/layout/choose_account_item.xml @@ -3,37 +3,21 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" android:orientation="horizontal" - android:paddingRight="6dip" - android:paddingBottom="2dip" - android:descendantFocusability="blocksDescendants" - android:gravity="center_vertical" > + android:background="#cccccc" + android:gravity="left|center_vertical"> - - - - - - - + android:singleLine="true" + android:ellipsize="end" + android:paddingTop="2dp" + android:paddingLeft="18dp" + android:paddingRight="4dp" + android:paddingBottom="2dp" + android:textColor="?android:attr/textColorPrimary" + android:textAppearance="?android:attr/textAppearanceSmall"/>
diff --git a/res/layout/choose_identity_item.xml b/res/layout/choose_identity_item.xml index 0f1999003..600bb8b75 100644 --- a/res/layout/choose_identity_item.xml +++ b/res/layout/choose_identity_item.xml @@ -5,25 +5,20 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:orientation="horizontal" - android:paddingRight="6dip" - android:paddingBottom="2dip" - android:descendantFocusability="blocksDescendants" - android:gravity="center_vertical" > + android:gravity="center_vertical"> + android:layout_width="6dp"/> + android:paddingLeft="12dp" + android:paddingRight="4dp"> + android:textAppearance="?android:attr/textAppearanceMedium"/> + android:textAppearance="?android:attr/textAppearanceSmall"/> diff --git a/res/layout/message_compose.xml b/res/layout/message_compose.xml index 2cafb2428..70d3be985 100644 --- a/res/layout/message_compose.xml +++ b/res/layout/message_compose.xml @@ -20,17 +20,34 @@ - + android:orientation="horizontal"> + + + +