From 50a092c3d6155fcd704990450640cf159072b1a2 Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Fri, 24 Jun 2011 14:40:03 -0700 Subject: [PATCH] Add IMAP support for the "replied" state in messages * Handle replied in new messages and updates * Code added for upsyncing replied state, but the CL for setting this state isn't yet submitted Change-Id: I6f3ff56475d70f686f96ed6a84fae3468f42b1c8 --- src/com/android/email/Controller.java | 67 ++++++++++++------- src/com/android/email/LegacyConversions.java | 17 +++-- .../android/email/MessagingController.java | 52 +++++++++++--- .../android/email/mail/store/ImapFolder.java | 5 +- 4 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java index f8a386f8b..217d5252d 100644 --- a/src/com/android/email/Controller.java +++ b/src/com/android/email/Controller.java @@ -584,17 +584,9 @@ public class Controller { // If this is a reply/forward, indicate it as such on the source. long sourceKey = message.mSourceKey; if (sourceKey != Message.NO_MESSAGE) { - Message source = Message.restoreMessageWithId(mProviderContext, sourceKey); - if (source != null) { - boolean isReply = (message.mFlags & Message.FLAG_TYPE_REPLY) != 0; - int flagUpdate = isReply ? Message.FLAG_REPLIED_TO : Message.FLAG_FORWARDED; - uri = ContentUris.withAppendedId(Message.CONTENT_URI, sourceKey); - cv.clear(); - cv.put(MessageColumns.FLAGS, source.mFlags | flagUpdate); - resolver.update(uri, cv, null, null); - } else { - Log.w(Logging.LOG_TAG, "Unable to find source message for a reply/forward"); - } + boolean isReply = (message.mFlags & Message.FLAG_TYPE_REPLY) != 0; + int flagUpdate = isReply ? Message.FLAG_REPLIED_TO : Message.FLAG_FORWARDED; + setMessageAnsweredOrForwarded(sourceKey, flagUpdate); } sendPendingMessages(accountId); @@ -871,6 +863,46 @@ public class Controller { return setMessageBoolean(messageId, EmailContent.MessageColumns.FLAG_READ, isRead); } + /** + * Update a message record and ping MessagingController, if necessary + * + * @param messageId the message to update + * @param cv the ContentValues used in the update + */ + private void updateMessageSync(long messageId, ContentValues cv) { + Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId); + mProviderContext.getContentResolver().update(uri, cv, null, null); + + // Service runs automatically, MessagingController needs a kick + long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId); + if (accountId == Account.NO_ACCOUNT) return; + if (isMessagingController(accountId)) { + mLegacyController.processPendingActions(accountId); + } + } + + /** + * Set the answered status of a message + * + * @param messageId the message to update + * @return the AsyncTask that will execute the changes (for testing only) + */ + public void setMessageAnsweredOrForwarded(final long messageId, + final int flag) { + EmailAsyncTask.runAsyncParallel(new Runnable() { + public void run() { + Message msg = Message.restoreMessageWithId(mProviderContext, messageId); + if (msg == null) { + Log.w(Logging.LOG_TAG, "Unable to find source message for a reply/forward"); + return; + } + ContentValues cv = new ContentValues(); + cv.put(MessageColumns.FLAGS, msg.mFlags | flag); + updateMessageSync(messageId, cv); + } + }); + } + /** * Set/clear the favorite status of a message * @@ -897,18 +929,7 @@ public class Controller { public void run() { ContentValues cv = new ContentValues(); cv.put(columnName, columnValue); - Uri uri = ContentUris.withAppendedId( - EmailContent.Message.SYNCED_CONTENT_URI, messageId); - mProviderContext.getContentResolver().update(uri, cv, null, null); - - // Service runs automatically, MessagingController needs a kick - long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId); - if (accountId == -1) { - return; - } - if (isMessagingController(accountId)) { - mLegacyController.processPendingActions(accountId); - } + updateMessageSync(messageId, cv); } }); } diff --git a/src/com/android/email/LegacyConversions.java b/src/com/android/email/LegacyConversions.java index 85bacac4a..fdefd3c4f 100644 --- a/src/com/android/email/LegacyConversions.java +++ b/src/com/android/email/LegacyConversions.java @@ -16,6 +16,13 @@ package com.android.email; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + import com.android.emailcommon.Logging; import com.android.emailcommon.internet.MimeBodyPart; import com.android.emailcommon.internet.MimeHeader; @@ -37,13 +44,6 @@ import com.android.emailcommon.utility.AttachmentUtilities; import org.apache.commons.io.IOUtils; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -96,6 +96,9 @@ public class LegacyConversions { localMessage.mSubject = subject; } localMessage.mFlagRead = message.isSet(Flag.SEEN); + if (message.isSet(Flag.ANSWERED)) { + localMessage.mFlags |= EmailContent.Message.FLAG_REPLIED_TO; + } // Keep the message in the "unloaded" state until it has (at least) a display name. // This prevents early flickering of empty messages in POP download. diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index 9e0b5befc..a6a1f8021 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -103,6 +103,7 @@ public class MessagingController implements Runnable { private static final Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN }; private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED }; + private static final Flag[] FLAG_LIST_ANSWERED = new Flag[] { Flag.ANSWERED }; /** * We write this into the serverId field of messages that will never be upsynced. @@ -388,10 +389,12 @@ public class MessagingController implements Runnable { private static final int COLUMN_FLAG_FAVORITE = 2; private static final int COLUMN_FLAG_LOADED = 3; private static final int COLUMN_SERVER_ID = 4; + private static final int COLUMN_FLAGS = 7; private static final String[] PROJECTION = new String[] { EmailContent.RECORD_ID, MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, MessageColumns.FLAG_LOADED, - SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY + SyncColumns.SERVER_ID, MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, + MessageColumns.FLAGS }; final long mId; @@ -399,6 +402,7 @@ public class MessagingController implements Runnable { final boolean mFlagFavorite; final int mFlagLoaded; final String mServerId; + final int mFlags; public LocalMessageInfo(Cursor c) { mId = c.getLong(COLUMN_ID); @@ -406,6 +410,7 @@ public class MessagingController implements Runnable { mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0; mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED); mServerId = c.getString(COLUMN_SERVER_ID); + mFlags = c.getInt(COLUMN_FLAGS); // Note: mailbox key and account key not needed - they are projected for the SELECT } } @@ -855,6 +860,7 @@ public class MessagingController implements Runnable { remoteFolder.fetch(remoteMessages, fp, null); boolean remoteSupportsSeen = false; boolean remoteSupportsFlagged = false; + boolean remoteSupportsAnswered = false; for (Flag flag : remoteFolder.getPermanentFlags()) { if (flag == Flag.SEEN) { remoteSupportsSeen = true; @@ -862,9 +868,12 @@ public class MessagingController implements Runnable { if (flag == Flag.FLAGGED) { remoteSupportsFlagged = true; } + if (flag == Flag.ANSWERED) { + remoteSupportsAnswered = true; + } } - // Update the SEEN & FLAGGED (star) flags (if supported remotely - e.g. not for POP3) - if (remoteSupportsSeen || remoteSupportsFlagged) { + // Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3) + if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) { for (Message remoteMessage : remoteMessages) { LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid()); if (localMessageInfo == null) { @@ -876,12 +885,22 @@ public class MessagingController implements Runnable { boolean localFlagged = localMessageInfo.mFlagFavorite; boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED); boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged)); - if (newSeen || newFlagged) { + int localFlags = localMessageInfo.mFlags; + boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0; + boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED); + boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered)); + if (newSeen || newFlagged || newAnswered) { Uri uri = ContentUris.withAppendedId( EmailContent.Message.CONTENT_URI, localMessageInfo.mId); ContentValues updateValues = new ContentValues(); - updateValues.put(EmailContent.Message.FLAG_READ, remoteSeen); - updateValues.put(EmailContent.Message.FLAG_FAVORITE, remoteFlagged); + updateValues.put(MessageColumns.FLAG_READ, remoteSeen); + updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged); + if (remoteAnswered) { + localFlags |= EmailContent.Message.FLAG_REPLIED_TO; + } else { + localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO; + } + updateValues.put(MessageColumns.FLAGS, localFlags); resolver.update(uri, updateValues, null, null); } } @@ -1307,6 +1326,7 @@ public class MessagingController implements Runnable { boolean changeRead = false; boolean changeFlagged = false; boolean changeMailbox = false; + boolean changeAnswered = false; EmailContent.Message oldMessage = EmailContent.getContent(updates, EmailContent.Message.class); @@ -1327,11 +1347,14 @@ public class MessagingController implements Runnable { } changeRead = oldMessage.mFlagRead != newMessage.mFlagRead; changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite; + changeAnswered = (oldMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != + (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO); } // Load the remote store if it will be needed if (remoteStore == null && - (changeMoveToTrash || changeRead || changeFlagged || changeMailbox)) { + (changeMoveToTrash || changeRead || changeFlagged || changeMailbox || + changeAnswered)) { remoteStore = Store.getInstance(account, mContext); } @@ -1340,9 +1363,9 @@ public class MessagingController implements Runnable { // Move message to trash processPendingMoveToTrash(remoteStore, account, mailbox, oldMessage, newMessage); - } else if (changeRead || changeFlagged || changeMailbox) { + } else if (changeRead || changeFlagged || changeMailbox || changeAnswered) { processPendingDataChange(remoteStore, mailbox, changeRead, changeFlagged, - changeMailbox, oldMessage, newMessage); + changeMailbox, changeAnswered, oldMessage, newMessage); } // Finally, delete the update @@ -1426,8 +1449,9 @@ public class MessagingController implements Runnable { * @param newMessage the current version of the message */ private void processPendingDataChange(Store remoteStore, Mailbox mailbox, boolean changeRead, - boolean changeFlagged, boolean changeMailbox, EmailContent.Message oldMessage, - final EmailContent.Message newMessage) throws MessagingException { + boolean changeFlagged, boolean changeMailbox, boolean changeAnswered, + EmailContent.Message oldMessage, final EmailContent.Message newMessage) + throws MessagingException { // New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't // being moved Mailbox newMailbox = mailbox; @@ -1465,6 +1489,8 @@ public class MessagingController implements Runnable { "Update for msg id=" + newMessage.mId + " read=" + newMessage.mFlagRead + " flagged=" + newMessage.mFlagFavorite + + " answered=" + + ((newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0) + " new mailbox=" + newMessage.mMailboxKey); } Message[] messages = new Message[] { remoteMessage }; @@ -1474,6 +1500,10 @@ public class MessagingController implements Runnable { if (changeFlagged) { remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite); } + if (changeAnswered) { + remoteFolder.setFlags(messages, FLAG_LIST_ANSWERED, + (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0); + } if (changeMailbox) { Folder toFolder = remoteStore.getFolder(newMailbox.mServerId); if (!remoteFolder.exists()) { diff --git a/src/com/android/email/mail/store/ImapFolder.java b/src/com/android/email/mail/store/ImapFolder.java index 32678ec25..79fc93e7a 100644 --- a/src/com/android/email/mail/store/ImapFolder.java +++ b/src/com/android/email/mail/store/ImapFolder.java @@ -62,7 +62,8 @@ import java.util.LinkedHashSet; import java.util.List; class ImapFolder extends Folder { - private final static Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN, Flag.FLAGGED }; + private final static Flag[] PERMANENT_FLAGS = + { Flag.DELETED, Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED }; private static final int COPY_BUFFER_SIZE = 16*1024; private final ImapStore mStore; @@ -993,6 +994,8 @@ class ImapFolder extends Folder { flagList.append(" " + ImapConstants.FLAG_DELETED); } else if (flag == Flag.FLAGGED) { flagList.append(" " + ImapConstants.FLAG_FLAGGED); + } else if (flag == Flag.ANSWERED) { + flagList.append(" " + ImapConstants.FLAG_ANSWERED); } } allFlags = flagList.substring(1);