Implement two part imap sync

b/10075523
Now, every 15 minutes we'll sync the last 24 hours.
Every 4 hours we'll perform a full sync, which will
take either the last 7 days, or until the oldest message
we already have locally.

Change-Id: Idc55a46a28af2a68cc324e414d51d88373941595
This commit is contained in:
Martin Hibdon 2013-08-06 16:50:11 -07:00
parent 9d628b4c41
commit c75f5880ab
5 changed files with 257 additions and 190 deletions

View File

@ -1649,6 +1649,8 @@ public abstract class EmailContent {
public static final String TOTAL_COUNT = "totalCount";
// The full hierarchical name of this folder, in the form a/b/c
public static final String HIERARCHICAL_NAME = "hierarchicalName";
// The last time that we did a full sync. Set from SystemClock.elapsedRealtime().
public static final String LAST_FULL_SYNC_TIME = "lastFullSyncTime";
}
public interface HostAuthColumns {

View File

@ -80,6 +80,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
public int mUiLastSyncResult;
public int mTotalCount;
public String mHierarchicalName;
public long mLastFullSyncTime;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
@ -101,6 +102,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
public static final int CONTENT_UI_LAST_SYNC_RESULT_COLUMN = 17;
public static final int CONTENT_TOTAL_COUNT_COLUMN = 18;
public static final int CONTENT_HIERARCHICAL_NAME_COLUMN = 19;
public static final int CONTENT_LAST_FULL_SYNC_COLUMN = 20;
/**
* <em>NOTE</em>: If fields are added or removed, the method {@link #getHashes()}
@ -114,7 +116,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
MailboxColumns.FLAGS, MailboxColumns.SYNC_STATUS, MailboxColumns.PARENT_KEY,
MailboxColumns.LAST_TOUCHED_TIME, MailboxColumns.UI_SYNC_STATUS,
MailboxColumns.UI_LAST_SYNC_RESULT, MailboxColumns.TOTAL_COUNT,
MailboxColumns.HIERARCHICAL_NAME
MailboxColumns.HIERARCHICAL_NAME, MailboxColumns.LAST_FULL_SYNC_TIME
};
/** Selection by server pathname for a given account */
@ -467,6 +469,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
mUiLastSyncResult = cursor.getInt(CONTENT_UI_LAST_SYNC_RESULT_COLUMN);
mTotalCount = cursor.getInt(CONTENT_TOTAL_COUNT_COLUMN);
mHierarchicalName = cursor.getString(CONTENT_HIERARCHICAL_NAME_COLUMN);
mLastFullSyncTime = cursor.getInt(CONTENT_LAST_FULL_SYNC_COLUMN);
}
@Override
@ -491,6 +494,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
values.put(MailboxColumns.UI_LAST_SYNC_RESULT, mUiLastSyncResult);
values.put(MailboxColumns.TOTAL_COUNT, mTotalCount);
values.put(MailboxColumns.HIERARCHICAL_NAME, mHierarchicalName);
values.put(MailboxColumns.LAST_FULL_SYNC_TIME, mLastFullSyncTime);
return values;
}
@ -504,6 +508,21 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
ContentValues values = new ContentValues();
values.put(MailboxColumns.TOTAL_COUNT, count);
update(c, values);
mTotalCount = count;
}
}
/**
* Store the last full sync time in the database.
* @param c
* @param syncTime
*/
public void updateLastFullSyncTime(final Context c, final long syncTime) {
if (syncTime != mLastFullSyncTime) {
ContentValues values = new ContentValues();
values.put(MailboxColumns.LAST_FULL_SYNC_TIME, syncTime);
update(c, values);
mLastFullSyncTime = syncTime;
}
}
@ -695,6 +714,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
dest.writeInt(mUiLastSyncResult);
dest.writeInt(mTotalCount);
dest.writeString(mHierarchicalName);
dest.writeLong(mLastFullSyncTime);
}
public Mailbox(Parcel in) {
@ -719,6 +739,7 @@ public class Mailbox extends EmailContent implements MailboxColumns, Parcelable
mUiLastSyncResult = in.readInt();
mTotalCount = in.readInt();
mHierarchicalName = in.readString();
mLastFullSyncTime = in.readLong();
}
public static final Parcelable.Creator<Mailbox> CREATOR = new Parcelable.Creator<Mailbox>() {

View File

@ -146,8 +146,9 @@ public final class DBHelper {
// Version 112: Convert Mailbox syncInterval to a boolean (whether or not this mailbox
// syncs along with the account).
// Version 113: Restore message_count to being useful.
// Version 114: Add lastFullSyncTime column
public static final int DATABASE_VERSION = 113;
public static final int DATABASE_VERSION = 114;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@ -435,7 +436,8 @@ public final class DBHelper {
+ MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY + " integer not null default 0, "
+ MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT + " integer not null default 0, "
+ MailboxColumns.TOTAL_COUNT + " integer, "
+ MailboxColumns.HIERARCHICAL_NAME + " text"
+ MailboxColumns.HIERARCHICAL_NAME + " text, "
+ MailboxColumns.LAST_FULL_SYNC_TIME + " integer"
+ ");";
db.execSQL("create table " + Mailbox.TABLE_NAME + s);
db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID
@ -1057,6 +1059,19 @@ public final class DBHelper {
recalculateMessageCount(db);
createMessageCountTriggers(db);
}
if (oldVersion <= 113) {
try {
db.execSQL("alter table " + Mailbox.TABLE_NAME
+ " add column " + MailboxColumns.LAST_FULL_SYNC_TIME +" integer" + ";");
ContentValues cv = new ContentValues();
cv.put(MailboxColumns.LAST_FULL_SYNC_TIME, 0);
db.update(Mailbox.TABLE_NAME, cv, null, null);
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
LogUtils.w(TAG, "Exception upgrading EmailProvider.db from v113 to v114", e);
}
}
}
@Override

View File

@ -27,7 +27,9 @@ import android.net.TrafficStats;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.android.email.LegacyConversions;
import com.android.email.NotificationController;
@ -69,7 +71,10 @@ import java.util.HashSet;
public class ImapService extends Service {
// TODO get these from configurations or settings.
private static final long DEFAULT_SYNC_WINDOW_MILLIS = 24 * 60 * 60 * 1000;
private static final long QUICK_SYNC_WINDOW_MILLIS = DateUtils.DAY_IN_MILLIS;
private static final long FULL_SYNC_WINDOW_MILLIS = 7 * DateUtils.DAY_IN_MILLIS;
private static final long FULL_SYNC_INTERVAL_MILLIS = 4 * DateUtils.HOUR_IN_MILLIS;
private static final int MINIMUM_MESSAGES_TO_SYNC = 10;
private static final int LOAD_MORE_MIN_INCREMENT = 10;
private static final int LOAD_MORE_MAX_INCREMENT = 20;
@ -93,7 +98,7 @@ public class ImapService extends Service {
* shouldn't be an issue
*/
private static final HashMap<Long, SortableMessage[]> sSearchResults =
new HashMap<Long, SortableMessage[]>();
new HashMap<Long, SortableMessage[]>();
/**
* We write this into the serverId field of messages that will never be upsynced.
@ -153,12 +158,13 @@ public class ImapService extends Service {
* @throws MessagingException
*/
public static int synchronizeMailboxSynchronous(Context context, final Account account,
final Mailbox folder, final boolean loadMore) throws MessagingException {
final Mailbox folder, final boolean loadMore, final boolean uiRefresh)
throws MessagingException {
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
NotificationController nc = NotificationController.getInstance(context);
try {
processPendingActionsSynchronous(context, account);
synchronizeMailboxGeneric(context, account, folder, loadMore);
synchronizeMailboxGeneric(context, account, folder, loadMore, uiRefresh);
// Clear authentication notification for this account
nc.cancelLoginFailedNotification(account.mId);
} catch (MessagingException e) {
@ -323,42 +329,190 @@ public class ImapService extends Service {
*
* @param account the account to sync
* @param mailbox the mailbox to sync
* @param deltaMessageCount requested change in number of messages to sync
* @param loadMore whether we should be loading more older messages
* @param uiRefresh whether this request is in response to a user action
* @return results of the sync pass
* @throws MessagingException
*/
private static void synchronizeMailboxGeneric(final Context context, final Account account,
final Mailbox mailbox, final boolean loadMore) throws MessagingException {
/*
* A list of IDs for messages that were downloaded and did not have the seen flag set.
* This serves as the "true" new message count reported to the user via notification.
*/
LogUtils.v(Logging.LOG_TAG, "synchronizeMailboxGeneric " + account + " " + mailbox +
" " + loadMore);
final Mailbox mailbox, final boolean loadMore, final boolean uiRefresh)
throws MessagingException {
LogUtils.v(Logging.LOG_TAG, "synchronizeMailboxGeneric " + account + " " + mailbox + " "
+ loadMore + " " + uiRefresh);
final ArrayList<Long> unseenMessages = new ArrayList<Long>();
ContentResolver resolver = context.getContentResolver();
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
// 0. We do not ever sync DRAFTS or OUTBOX (down or up)
if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) {
return;
}
// 1. Get the message list from the local store and create an index of the uids
// 1. Figure out what our sync window should be.
long endDate;
// We will do a full sync if the user has actively requested a sync, or if it has been
// too long since the last full sync.
// If we have rebooted since the last full sync, then we may get a negative
// timeSinceLastFullSync. In this case, we don't know how long it's been since the last
// full sync so we should perform the full sync.
final long timeSinceLastFullSync = SystemClock.elapsedRealtime() -
mailbox.mLastFullSyncTime;
final boolean fullSync = (uiRefresh || loadMore ||
timeSinceLastFullSync >= FULL_SYNC_INTERVAL_MILLIS || timeSinceLastFullSync < 0);
if (fullSync) {
// Find the oldest message in the local store. We need our time window to include
// all messages that are currently present locally.
endDate = System.currentTimeMillis() - FULL_SYNC_WINDOW_MILLIS;
Cursor localOldestCursor = null;
try {
localOldestCursor = resolver.query(EmailContent.Message.CONTENT_URI,
OldestTimestampInfo.PROJECTION,
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + " AND " +
MessageColumns.MAILBOX_KEY + "=?",
new String[] {String.valueOf(account.mId), String.valueOf(mailbox.mId)},
null);
if (localOldestCursor != null && localOldestCursor.moveToFirst()) {
long oldestLocalMessageDate = localOldestCursor.getLong(
OldestTimestampInfo.COLUMN_OLDEST_TIMESTAMP);
if (oldestLocalMessageDate > 0) {
endDate = Math.min(endDate, oldestLocalMessageDate);
LogUtils.d(
Logging.LOG_TAG, "oldest local message " + oldestLocalMessageDate);
}
}
} finally {
if (localOldestCursor != null) {
localOldestCursor.close();
}
}
LogUtils.d(Logging.LOG_TAG, "full sync: original window: now - " + endDate);
} else {
// We are doing a frequent, quick sync. This only syncs a small time window, so that
// we wil get any new messages, but not spend a lot of bandwidth downloading
// messageIds that we most likely already have.
endDate = System.currentTimeMillis() - QUICK_SYNC_WINDOW_MILLIS;
LogUtils.d(Logging.LOG_TAG, "quick sync: original window: now - " + endDate);
}
// 2. Open the remote folder and create the remote folder if necessary
Store remoteStore = Store.getInstance(account, context);
// The account might have been deleted
if (remoteStore == null) return;
final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
// If the folder is a "special" folder we need to see if it exists
// on the remote server. It if does not exist we'll try to create it. If we
// can't create we'll abort. This will happen on every single Pop3 folder as
// designed and on Imap folders during error conditions. This allows us
// to treat Pop3 and Imap the same in this code.
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT) {
if (!remoteFolder.exists()) {
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
return;
}
}
}
remoteFolder.open(OpenMode.READ_WRITE);
// 3. Trash any remote messages that are marked as trashed locally.
// TODO - this comment was here, but no code was here.
// 4. Get the number of messages on the server.
final int remoteMessageCount = remoteFolder.getMessageCount();
// 5. Save folder message count locally.
mailbox.updateMessageCount(context, remoteMessageCount);
// 6. Get all message Ids in our sync window:
Message[] remoteMessages;
remoteMessages = remoteFolder.getMessages(0, endDate, null);
LogUtils.d(Logging.LOG_TAG, "received " + remoteMessages.length + " messages");
// 7. See if we need any additional messages beyond our date query range results.
// If we do, keep increasing the size of our query window until we have
// enough, or until we have all messages in the mailbox.
int totalCountNeeded;
if (loadMore) {
totalCountNeeded = remoteMessages.length + LOAD_MORE_MIN_INCREMENT;
} else {
totalCountNeeded = remoteMessages.length;
if (fullSync && totalCountNeeded < MINIMUM_MESSAGES_TO_SYNC) {
totalCountNeeded = MINIMUM_MESSAGES_TO_SYNC;
}
}
LogUtils.d(Logging.LOG_TAG, "need " + totalCountNeeded + " total");
final int additionalMessagesNeeded = totalCountNeeded - remoteMessages.length;
if (additionalMessagesNeeded > 0) {
LogUtils.d(Logging.LOG_TAG, "trying to get " + additionalMessagesNeeded + " more");
long startDate = endDate - 1;
Message[] additionalMessages = new Message[0];
long windowIncreaseSize = INITIAL_WINDOW_SIZE_INCREASE;
while (additionalMessages.length < additionalMessagesNeeded && endDate > 0) {
endDate = endDate - windowIncreaseSize;
if (endDate < 0) {
LogUtils.d(Logging.LOG_TAG, "window size too large, this is the last attempt");
endDate = 0;
}
LogUtils.d(Logging.LOG_TAG,
"requesting additional messages from range " + startDate + " - " + endDate);
additionalMessages = remoteFolder.getMessages(startDate, endDate, null);
// If don't get enough messages with the first window size expansion,
// we need to accelerate rate at which the window expands. Otherwise,
// if there were no messages for several weeks, we'd always end up
// performing dozens of queries.
windowIncreaseSize *= 2;
}
LogUtils.d(Logging.LOG_TAG, "additionalMessages " + additionalMessages.length);
if (additionalMessages.length < additionalMessagesNeeded) {
// We have attempted to load a window that goes all the way back to time zero,
// but we still don't have as many messages as the server says are in the inbox.
// This is not expected to happen.
LogUtils.e(Logging.LOG_TAG, "expected to find " + additionalMessagesNeeded
+ " more messages, only got " + additionalMessages.length);
}
int additionalToKeep = additionalMessages.length;
if (additionalMessages.length > LOAD_MORE_MAX_INCREMENT) {
// We have way more additional messages than intended, drop some of them.
// The last messages are the most recent, so those are the ones we need to keep.
additionalToKeep = LOAD_MORE_MAX_INCREMENT;
}
// Copy the messages into one array.
Message[] allMessages = new Message[remoteMessages.length + additionalToKeep];
System.arraycopy(remoteMessages, 0, allMessages, 0, remoteMessages.length);
// additionalMessages may have more than we need, only copy the last
// several. These are the most recent messages in that set because
// of the way IMAP server returns messages.
System.arraycopy(additionalMessages, additionalMessages.length - additionalToKeep,
allMessages, remoteMessages.length, additionalToKeep);
remoteMessages = allMessages;
}
// 8. Get the all of the local messages within the sync window, and create
// an index of the uids.
// It's important that we only get local messages that are inside our sync window.
// In a later step, we will delete any messages that are in localMessageMap but
// are not in our remote message list.
Cursor localUidCursor = null;
HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>();
try {
localUidCursor = resolver.query(
EmailContent.Message.CONTENT_URI,
LocalMessageInfo.PROJECTION,
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
" AND " + MessageColumns.MAILBOX_KEY + "=?",
EmailContent.MessageColumns.ACCOUNT_KEY + "=?"
+ " AND " + MessageColumns.MAILBOX_KEY + "=?"
+ " AND " + MessageColumns.TIMESTAMP + ">=?",
new String[] {
String.valueOf(account.mId),
String.valueOf(mailbox.mId)
},
String.valueOf(mailbox.mId),
String.valueOf(endDate) },
null);
while (localUidCursor.moveToNext()) {
LocalMessageInfo info = new LocalMessageInfo(localUidCursor);
@ -376,148 +530,16 @@ public class ImapService extends Service {
}
}
// 2. Open the remote folder and create the remote folder if necessary
Store remoteStore = Store.getInstance(account, context);
// The account might have been deleted
if (remoteStore == null) return;
final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId);
/*
* If the folder is a "special" folder we need to see if it exists
* on the remote server. It if does not exist we'll try to create it. If we
* can't create we'll abort. This will happen on every single Pop3 folder as
* designed and on Imap folders during error conditions. This allows us
* to treat Pop3 and Imap the same in this code.
*/
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT) {
if (!remoteFolder.exists()) {
if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) {
return;
}
}
}
// 3, Open the remote folder. This pre-loads certain metadata like message count.
remoteFolder.open(OpenMode.READ_WRITE);
// 4. Trash any remote messages that are marked as trashed locally.
// TODO - this comment was here, but no code was here.
// 5. Get the number of messages on the server.
final int remoteMessageCount = remoteFolder.getMessageCount();
// 6. Save folder message count that we got earlier.
mailbox.updateMessageCount(context, remoteMessageCount);
// 7. Figure out how big our sync window should be. Leave startDate set to zero, this
// indicates we do not want any constraint on the BEFORE parameter sent in our query.
// This way, we will always be able to get the most recent messages, even if the
// imap server's date is different from ours.
long startDate = 0;
long endDate = System.currentTimeMillis() - DEFAULT_SYNC_WINDOW_MILLIS;
LogUtils.d(Logging.LOG_TAG, "original window " + startDate + " - " + endDate);
Cursor localOldestCursor = null;
try {
localOldestCursor = resolver.query(
EmailContent.Message.CONTENT_URI,
OldestTimestampInfo.PROJECTION,
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
" AND " + MessageColumns.MAILBOX_KEY + "=?",
new String[] {
String.valueOf(account.mId),
String.valueOf(mailbox.mId)
},
null);
if (localOldestCursor != null && localOldestCursor.moveToFirst()) {
long oldestLocalMessageDate = localOldestCursor.getLong(
OldestTimestampInfo.COLUMN_OLDEST_TIMESTAMP);
if (oldestLocalMessageDate > 0) {
endDate = Math.min(endDate, oldestLocalMessageDate);
LogUtils.d(Logging.LOG_TAG, "oldest local message " + oldestLocalMessageDate);
}
}
} finally {
if (localOldestCursor != null) {
localOldestCursor.close();
}
}
// Get all messages in our query date range:
Message[] remoteMessages;
remoteMessages = remoteFolder.getMessages(startDate, endDate, null);
// See if we need any additional messages beyond our date query range results.
// If we do, keep increasing the size of our query window until we have
// enough, or until we have all messages in the mailbox.
LogUtils.d(Logging.LOG_TAG, "received " + remoteMessages.length + " messages");
int totalCountNeeded = Math.max(remoteMessages.length, MINIMUM_MESSAGES_TO_SYNC);
if (loadMore) {
totalCountNeeded += LOAD_MORE_MIN_INCREMENT;
}
totalCountNeeded = Math.min(remoteMessageCount, totalCountNeeded);
LogUtils.d(Logging.LOG_TAG, "need " + totalCountNeeded + " total");
final int additionalMessagesNeeded = totalCountNeeded - remoteMessages.length;
if (additionalMessagesNeeded > 0) {
LogUtils.d(Logging.LOG_TAG, "trying to get " + additionalMessagesNeeded + " more");
startDate = endDate - 1;
Message[] additionalMessages = new Message[0];
long windowIncreaseSize = INITIAL_WINDOW_SIZE_INCREASE;
while (additionalMessages.length < additionalMessagesNeeded && endDate > 0) {
endDate = endDate - windowIncreaseSize;
if (endDate < 0) {
LogUtils.d(Logging.LOG_TAG, "window size too large, this is the last attempt");
endDate = 0;
}
LogUtils.d(Logging.LOG_TAG, "requesting additional messages from range " +
startDate + " - " + endDate);
additionalMessages = remoteFolder.getMessages(startDate, endDate, null);
// If don't get enough messages with the first window size expansion,
// we need to accelerate rate at which the window expands. Otherwise,
// if there were no messages for several weeks, we'd always end up
// performing dozens of queries.
windowIncreaseSize *= 2;
}
LogUtils.d(Logging.LOG_TAG, "additionalMessages " + additionalMessages.length);
if (additionalMessages.length < additionalMessagesNeeded) {
// We have attempted to load a window that goes all the way back to time zero,
// but we still don't have as many messages as the server says are in the inbox.
// This is not expected to happen.
LogUtils.e(Logging.LOG_TAG, "expected to find " + additionalMessagesNeeded +
" more messages, only got " + additionalMessages.length);
}
int additionalToKeep = additionalMessages.length;
if (additionalMessages.length > LOAD_MORE_MAX_INCREMENT) {
// We have way more additional messages than intended, drop some of them.
// The last messages are the most recent, so those are the ones we need to keep.
additionalToKeep = LOAD_MORE_MAX_INCREMENT;
}
// Copy the messages into one array.
Message[] allMessages = new Message[remoteMessages.length + additionalToKeep];
System.arraycopy(remoteMessages, 0, allMessages, 0, remoteMessages.length);
// additionalMessages may have more than we need, only copy the last
// several. These are the most recent messages in that set because of the
// way IMAP server returns messages.
System.arraycopy(additionalMessages, additionalMessages.length - additionalToKeep,
allMessages, remoteMessages.length, additionalToKeep);
remoteMessages = allMessages;
}
/*
* 8. Get a list of the messages that are in the remote list but not on the
* local store, or messages that are in the local store but failed to download
* on the last sync. These are the new messages that we will download.
* Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
* because they are locally deleted and we don't need or want the old message from
* the server.
*/
// 9. Get a list of the messages that are in the remote list but not on the
// local store, or messages that are in the local store but failed to download
// on the last sync. These are the new messages that we will download.
// Note, we also skip syncing messages which are flagged as "deleted message" sentinels,
// because they are locally deleted and we don't need or want the old message from
// the server.
final ArrayList<Message> unsyncedMessages = new ArrayList<Message>();
final HashMap<String, Message> remoteUidMap = new HashMap<String, Message>();
// Process the messages in the reverse order we received them in. This means that
// we process the most recent one first, which gives a better user experience.
// we load the most recent one first, which gives a better user experience.
for (int i = remoteMessages.length - 1; i >= 0; i--) {
Message message = remoteMessages[i];
LogUtils.d(Logging.LOG_TAG, "remote message " + message.getUid());
@ -538,7 +560,7 @@ public class ImapService extends Service {
}
}
// 9. Download basic info about the new/unloaded messages (if any)
// 10. Download basic info about the new/unloaded messages (if any)
/*
* Fetch the flags and envelope only of the new messages. This is intended to get us
* critical data as fast as possible, and then we'll fill in the details.
@ -548,7 +570,8 @@ public class ImapService extends Service {
localMessageMap, unseenMessages);
}
// 10. Refresh the flags for any messages in the local store that we didn't just download.
// 11. Refresh the flags for any messages in the local store that we
// didn't just download.
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.FLAGS);
remoteFolder.fetch(remoteMessages, fp, null);
@ -567,7 +590,7 @@ public class ImapService extends Service {
}
}
// 11. Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3)
// 12. 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());
@ -601,7 +624,8 @@ public class ImapService extends Service {
}
}
// 12. Remove any messages that are in the local store but no longer on the remote store.
// 13. Remove messages that are in the local store and in the current sync window,
// but no longer on the remote store.
final HashSet<String> localUidsToDelete = new HashSet<String>(localMessageMap.keySet());
localUidsToDelete.removeAll(remoteUidMap.keySet());
@ -610,8 +634,7 @@ public class ImapService extends Service {
// Delete associated data (attachment files)
// Attachment & Body records are auto-deleted when we delete the Message record
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId,
infoToDelete.mId);
AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, infoToDelete.mId);
// Delete the message itself
Uri uriToDelete = ContentUris.withAppendedId(
@ -629,7 +652,11 @@ public class ImapService extends Service {
loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox);
// 13. Clean up and report results
if (fullSync) {
mailbox.updateLastFullSyncTime(context, SystemClock.elapsedRealtime());
}
// 14. Clean up and report results
remoteFolder.close(false);
}
@ -649,7 +676,7 @@ public class ImapService extends Service {
* @throws MessagingException
*/
private static void processPendingActionsSynchronous(Context context, Account account)
throws MessagingException {
throws MessagingException {
TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account));
String[] accountIdArgs = new String[] { Long.toString(account.mId) };
@ -666,12 +693,13 @@ public class ImapService extends Service {
/**
* Get the mailbox corresponding to the remote location of a message; this will normally be
* the mailbox whose _id is mailboxKey, except for search results, where we must look it up
* by serverId
* by serverId.
*
* @param message the message in question
* @return the mailbox in which the message resides on the server
*/
private static Mailbox getRemoteMailboxForMessage(Context context,
EmailContent.Message message) {
private static Mailbox getRemoteMailboxForMessage(
Context context, EmailContent.Message message) {
// If this is a search result, use the protocolSearchInfo field to get the server info
if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
long accountKey = message.mAccountKey;
@ -682,7 +710,7 @@ public class ImapService extends Service {
}
Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION,
new String[] {protocolSearchInfo, Long.toString(accountKey)},
new String[] {protocolSearchInfo, Long.toString(accountKey) },
null);
try {
if (c.moveToNext()) {
@ -760,7 +788,7 @@ public class ImapService extends Service {
// no point in continuing through the rest of the pending updates.
if (MailActivityEmail.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "Unable to process pending delete for id="
+ lastMessageId + ": " + me);
+ lastMessageId + ": " + me);
}
} finally {
deletes.close();
@ -769,12 +797,12 @@ public class ImapService extends Service {
/**
* Scan for messages that are in Sent, and are in need of upload,
* and send them to the server. "In need of upload" is defined as:
* and send them to the server. "In need of upload" is defined as:
* serverId == null (no UID has been assigned)
* or
* message is in the updated list
*
* Note we also look for messages that are moving from drafts->outbox->sent. They never
* Note we also look for messages that are moving from drafts->outbox->sent. They never
* go through "drafts" or "outbox" on the server, so we hang onto these until they can be
* uploaded directly to the Sent folder.
*
@ -907,10 +935,10 @@ public class ImapService extends Service {
boolean changeAnswered = false;
EmailContent.Message oldMessage =
EmailContent.getContent(updates, EmailContent.Message.class);
EmailContent.getContent(updates, EmailContent.Message.class);
lastMessageId = oldMessage.mId;
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(context, oldMessage.mId);
EmailContent.Message.restoreMessageWithId(context, oldMessage.mId);
if (newMessage != null) {
mailbox = Mailbox.restoreMailboxWithId(context, newMessage.mMailboxKey);
if (mailbox == null) {
@ -926,8 +954,8 @@ public class ImapService extends Service {
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);
}
(newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO);
}
// Load the remote store if it will be needed
if (remoteStore == null &&
@ -957,7 +985,7 @@ public class ImapService extends Service {
// no point in continuing through the rest of the pending updates.
if (MailActivityEmail.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "Unable to process pending update for id="
+ lastMessageId + ": " + me);
+ lastMessageId + ": " + me);
}
} finally {
updates.close();
@ -965,14 +993,14 @@ public class ImapService extends Service {
}
/**
* Upsync an entire message. This must also unwind whatever triggered it (either by
* Upsync an entire message. This must also unwind whatever triggered it (either by
* updating the serverId, or by deleting the update record, or it's going to keep happening
* over and over again.
*
* Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
* This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
* Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload.
* This is to avoid unnecessary uploads into the trash. Although the caller attempts to select
* only the Drafts and Sent folders, this can happen when the update record and the current
* record mismatch. In this case, we let the update record remain, because the filters
* record mismatch. In this case, we let the update record remain, because the filters
* in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it)
* appropriately.
*
@ -986,7 +1014,7 @@ public class ImapService extends Service {
Account account, Mailbox mailbox, long messageId)
throws MessagingException {
EmailContent.Message newMessage =
EmailContent.Message.restoreMessageWithId(context, messageId);
EmailContent.Message.restoreMessageWithId(context, messageId);
final boolean deleteUpdate;
if (newMessage == null) {
deleteUpdate = true;
@ -1100,6 +1128,7 @@ public class ImapService extends Service {
context.getContentResolver().update(ContentUris.withAppendedId(
EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null);
}
@Override
public void onMessageNotFound(Message message) {
}
@ -1172,7 +1201,7 @@ public class ImapService extends Service {
remoteTrashFolder.create(FolderType.HOLDS_MESSAGES);
}
// 7. Try to copy the message into the remote trash folder
// 7. Try to copy the message into the remote trash folder
// Note, this entire section will be skipped for POP3 because there's no remote trash
if (remoteTrashFolder.exists()) {
/*
@ -1434,7 +1463,7 @@ public class ImapService extends Service {
final int numSearchResults = sortableMessages.length;
final int numToLoad =
Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit);
if (numToLoad <= 0) {
return 0;
}
@ -1456,7 +1485,7 @@ public class ImapService extends Service {
public void messageRetrieved(Message message) {
try {
// Determine if the new message was already known (e.g. partial)
// And create or reload the full message info
// And create or reload the full message info.
EmailContent.Message localMessage = new EmailContent.Message();
try {
// Copy the fields that are available into the message

View File

@ -134,7 +134,7 @@ public class PopImapSyncAdapterService extends Service {
final int status;
if (protocol.equals(legacyImapProtocol)) {
status = ImapService.synchronizeMailboxSynchronous(context, account,
mailbox, deltaMessageCount != 0);
mailbox, deltaMessageCount != 0, uiRefresh);
} else {
status = Pop3Service.synchronizeMailboxSynchronous(context, account,
mailbox, deltaMessageCount);