15c523858385176c33a7456bb84035de78552d22dMarc Blank/* 25c523858385176c33a7456bb84035de78552d22dMarc Blank * Copyright (C) 2012 The Android Open Source Project 35c523858385176c33a7456bb84035de78552d22dMarc Blank * 45c523858385176c33a7456bb84035de78552d22dMarc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 55c523858385176c33a7456bb84035de78552d22dMarc Blank * you may not use this file except in compliance with the License. 65c523858385176c33a7456bb84035de78552d22dMarc Blank * You may obtain a copy of the License at 75c523858385176c33a7456bb84035de78552d22dMarc Blank * 85c523858385176c33a7456bb84035de78552d22dMarc Blank * http://www.apache.org/licenses/LICENSE-2.0 95c523858385176c33a7456bb84035de78552d22dMarc Blank * 105c523858385176c33a7456bb84035de78552d22dMarc Blank * Unless required by applicable law or agreed to in writing, software 115c523858385176c33a7456bb84035de78552d22dMarc Blank * distributed under the License is distributed on an "AS IS" BASIS, 125c523858385176c33a7456bb84035de78552d22dMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 135c523858385176c33a7456bb84035de78552d22dMarc Blank * See the License for the specific language governing permissions and 145c523858385176c33a7456bb84035de78552d22dMarc Blank * limitations under the License. 155c523858385176c33a7456bb84035de78552d22dMarc Blank */ 165c523858385176c33a7456bb84035de78552d22dMarc Blank 175c523858385176c33a7456bb84035de78552d22dMarc Blankpackage com.android.email.service; 185c523858385176c33a7456bb84035de78552d22dMarc Blank 195c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.app.Service; 205c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.ContentResolver; 215c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.ContentUris; 225c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.ContentValues; 235c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.Context; 245c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.content.Intent; 255c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.database.Cursor; 265c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.net.TrafficStats; 275c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.net.Uri; 285c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.os.IBinder; 295c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.os.RemoteException; 30c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdonimport android.os.SystemClock; 315c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.text.TextUtils; 32c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdonimport android.text.format.DateUtils; 335c523858385176c33a7456bb84035de78552d22dMarc Blank 345c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.LegacyConversions; 355c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.NotificationController; 365c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.mail.Store; 375c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email.provider.Utilities; 385c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.email2.ui.MailActivityEmail; 395c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.Logging; 405c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.TrafficFlags; 415c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.internet.MimeUtility; 425c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.AuthenticationFailedException; 435c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.FetchProfile; 445c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Flag; 455c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder; 465c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder.FolderType; 475c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder.MessageRetrievalListener; 485c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder.MessageUpdateCallbacks; 495c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Folder.OpenMode; 505c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Message; 515c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.MessagingException; 525c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.mail.Part; 535c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.Account; 545c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.EmailContent; 555c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns; 565c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns; 575c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns; 585c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.provider.Mailbox; 595c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.service.EmailServiceStatus; 605c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.service.SearchParams; 615c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.utility.AttachmentUtilities; 62af52f20930b2c0f24eeecc10be903712802d0965Tony Mantlerimport com.android.mail.providers.UIProvider; 635c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.mail.providers.UIProvider.AccountCapabilities; 64560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 655c523858385176c33a7456bb84035de78552d22dMarc Blank 665c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.ArrayList; 675c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Arrays; 685c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Comparator; 69c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Huimport java.util.Date; 705c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.HashMap; 715c523858385176c33a7456bb84035de78552d22dMarc Blank 725c523858385176c33a7456bb84035de78552d22dMarc Blankpublic class ImapService extends Service { 7325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // TODO get these from configurations or settings. 74c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon private static final long QUICK_SYNC_WINDOW_MILLIS = DateUtils.DAY_IN_MILLIS; 75c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon private static final long FULL_SYNC_WINDOW_MILLIS = 7 * DateUtils.DAY_IN_MILLIS; 76c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon private static final long FULL_SYNC_INTERVAL_MILLIS = 4 * DateUtils.HOUR_IN_MILLIS; 77c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 7825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final int MINIMUM_MESSAGES_TO_SYNC = 10; 7925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final int LOAD_MORE_MIN_INCREMENT = 10; 8025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final int LOAD_MORE_MAX_INCREMENT = 20; 8125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final long INITIAL_WINDOW_SIZE_INCREASE = 24 * 60 * 60 * 1000; 8225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 835c523858385176c33a7456bb84035de78552d22dMarc Blank private static final Flag[] FLAG_LIST_SEEN = new Flag[] { Flag.SEEN }; 845c523858385176c33a7456bb84035de78552d22dMarc Blank private static final Flag[] FLAG_LIST_FLAGGED = new Flag[] { Flag.FLAGGED }; 855c523858385176c33a7456bb84035de78552d22dMarc Blank private static final Flag[] FLAG_LIST_ANSWERED = new Flag[] { Flag.ANSWERED }; 865c523858385176c33a7456bb84035de78552d22dMarc Blank 875c523858385176c33a7456bb84035de78552d22dMarc Blank /** 885c523858385176c33a7456bb84035de78552d22dMarc Blank * Simple cache for last search result mailbox by account and serverId, since the most common 895c523858385176c33a7456bb84035de78552d22dMarc Blank * case will be repeated use of the same mailbox 905c523858385176c33a7456bb84035de78552d22dMarc Blank */ 915c523858385176c33a7456bb84035de78552d22dMarc Blank private static long mLastSearchAccountKey = Account.NO_ACCOUNT; 925c523858385176c33a7456bb84035de78552d22dMarc Blank private static String mLastSearchServerId = null; 935c523858385176c33a7456bb84035de78552d22dMarc Blank private static Mailbox mLastSearchRemoteMailbox = null; 945c523858385176c33a7456bb84035de78552d22dMarc Blank 955c523858385176c33a7456bb84035de78552d22dMarc Blank /** 965c523858385176c33a7456bb84035de78552d22dMarc Blank * Cache search results by account; this allows for "load more" support without having to 975c523858385176c33a7456bb84035de78552d22dMarc Blank * redo the search (which can be quite slow). SortableMessage is a smallish class, so memory 985c523858385176c33a7456bb84035de78552d22dMarc Blank * shouldn't be an issue 995c523858385176c33a7456bb84035de78552d22dMarc Blank */ 1005c523858385176c33a7456bb84035de78552d22dMarc Blank private static final HashMap<Long, SortableMessage[]> sSearchResults = 101c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new HashMap<Long, SortableMessage[]>(); 1025c523858385176c33a7456bb84035de78552d22dMarc Blank 1035c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1045c523858385176c33a7456bb84035de78552d22dMarc Blank * We write this into the serverId field of messages that will never be upsynced. 1055c523858385176c33a7456bb84035de78552d22dMarc Blank */ 1065c523858385176c33a7456bb84035de78552d22dMarc Blank private static final String LOCAL_SERVERID_PREFIX = "Local-"; 1075c523858385176c33a7456bb84035de78552d22dMarc Blank 1085c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1095c523858385176c33a7456bb84035de78552d22dMarc Blank public int onStartCommand(Intent intent, int flags, int startId) { 1105c523858385176c33a7456bb84035de78552d22dMarc Blank return Service.START_STICKY; 1115c523858385176c33a7456bb84035de78552d22dMarc Blank } 1125c523858385176c33a7456bb84035de78552d22dMarc Blank 1135c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1145c523858385176c33a7456bb84035de78552d22dMarc Blank * Create our EmailService implementation here. 1155c523858385176c33a7456bb84035de78552d22dMarc Blank */ 1165c523858385176c33a7456bb84035de78552d22dMarc Blank private final EmailServiceStub mBinder = new EmailServiceStub() { 1175c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1185c523858385176c33a7456bb84035de78552d22dMarc Blank public void loadMore(long messageId) throws RemoteException { 1195c523858385176c33a7456bb84035de78552d22dMarc Blank // We don't do "loadMore" for IMAP messages; the sync should handle this 1205c523858385176c33a7456bb84035de78552d22dMarc Blank } 1215c523858385176c33a7456bb84035de78552d22dMarc Blank 1225c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1235c523858385176c33a7456bb84035de78552d22dMarc Blank public int searchMessages(long accountId, SearchParams searchParams, long destMailboxId) { 1245c523858385176c33a7456bb84035de78552d22dMarc Blank try { 1255c523858385176c33a7456bb84035de78552d22dMarc Blank return searchMailboxImpl(getApplicationContext(), accountId, searchParams, 1265c523858385176c33a7456bb84035de78552d22dMarc Blank destMailboxId); 1275c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException e) { 1287d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler // Ignore 1295c523858385176c33a7456bb84035de78552d22dMarc Blank } 1305c523858385176c33a7456bb84035de78552d22dMarc Blank return 0; 1315c523858385176c33a7456bb84035de78552d22dMarc Blank } 1325c523858385176c33a7456bb84035de78552d22dMarc Blank 1335c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1345c523858385176c33a7456bb84035de78552d22dMarc Blank public int getCapabilities(Account acct) throws RemoteException { 1355c523858385176c33a7456bb84035de78552d22dMarc Blank return AccountCapabilities.SYNCABLE_FOLDERS | 1365c523858385176c33a7456bb84035de78552d22dMarc Blank AccountCapabilities.FOLDER_SERVER_SEARCH | 1376edccbf1f1371157cfa6e503d8353a474aafd2a1Yu Ping Hu AccountCapabilities.UNDO | 1386edccbf1f1371157cfa6e503d8353a474aafd2a1Yu Ping Hu AccountCapabilities.DISCARD_CONVERSATION_DRAFTS; 1395c523858385176c33a7456bb84035de78552d22dMarc Blank } 1405c523858385176c33a7456bb84035de78552d22dMarc Blank 1415c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1425c523858385176c33a7456bb84035de78552d22dMarc Blank public void serviceUpdated(String emailAddress) throws RemoteException { 1435c523858385176c33a7456bb84035de78552d22dMarc Blank // Not needed 1445c523858385176c33a7456bb84035de78552d22dMarc Blank } 1455c523858385176c33a7456bb84035de78552d22dMarc Blank }; 1465c523858385176c33a7456bb84035de78552d22dMarc Blank 1475c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 1485c523858385176c33a7456bb84035de78552d22dMarc Blank public IBinder onBind(Intent intent) { 1492075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu mBinder.init(this); 1505c523858385176c33a7456bb84035de78552d22dMarc Blank return mBinder; 1515c523858385176c33a7456bb84035de78552d22dMarc Blank } 1525c523858385176c33a7456bb84035de78552d22dMarc Blank 1535c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1545c523858385176c33a7456bb84035de78552d22dMarc Blank * Start foreground synchronization of the specified folder. This is called by 1555c523858385176c33a7456bb84035de78552d22dMarc Blank * synchronizeMailbox or checkMail. 1565c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO this should use ID's instead of fully-restored objects 1572075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu * @return The status code for whether this operation succeeded. 1585c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 1595c523858385176c33a7456bb84035de78552d22dMarc Blank */ 16037b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon public static synchronized int synchronizeMailboxSynchronous(Context context, 16137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon final Account account, final Mailbox folder, final boolean loadMore, 16237b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon final boolean uiRefresh) throws MessagingException { 1635c523858385176c33a7456bb84035de78552d22dMarc Blank TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account)); 1645c523858385176c33a7456bb84035de78552d22dMarc Blank NotificationController nc = NotificationController.getInstance(context); 1655c523858385176c33a7456bb84035de78552d22dMarc Blank try { 1665c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingActionsSynchronous(context, account); 167c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon synchronizeMailboxGeneric(context, account, folder, loadMore, uiRefresh); 1685c523858385176c33a7456bb84035de78552d22dMarc Blank // Clear authentication notification for this account 1695c523858385176c33a7456bb84035de78552d22dMarc Blank nc.cancelLoginFailedNotification(account.mId); 1705c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException e) { 1715c523858385176c33a7456bb84035de78552d22dMarc Blank if (Logging.LOGD) { 172466eb2dcd2660bf0e5d8d1440db598a30ca184f3Martin Hibdon LogUtils.d(Logging.LOG_TAG, "synchronizeMailboxSynchronous", e); 1735c523858385176c33a7456bb84035de78552d22dMarc Blank } 1745c523858385176c33a7456bb84035de78552d22dMarc Blank if (e instanceof AuthenticationFailedException) { 1755c523858385176c33a7456bb84035de78552d22dMarc Blank // Generate authentication notification 1765c523858385176c33a7456bb84035de78552d22dMarc Blank nc.showLoginFailedNotification(account.mId); 1775c523858385176c33a7456bb84035de78552d22dMarc Blank } 1785c523858385176c33a7456bb84035de78552d22dMarc Blank throw e; 1795c523858385176c33a7456bb84035de78552d22dMarc Blank } 1802075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu // TODO: Rather than use exceptions as logic above, return the status and handle it 1812075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu // correctly in caller. 1822075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu return EmailServiceStatus.SUCCESS; 1835c523858385176c33a7456bb84035de78552d22dMarc Blank } 1845c523858385176c33a7456bb84035de78552d22dMarc Blank 1855c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1865c523858385176c33a7456bb84035de78552d22dMarc Blank * Lightweight record for the first pass of message sync, where I'm just seeing if 1875c523858385176c33a7456bb84035de78552d22dMarc Blank * the local message requires sync. Later (for messages that need syncing) we'll do a full 1885c523858385176c33a7456bb84035de78552d22dMarc Blank * readout from the DB. 1895c523858385176c33a7456bb84035de78552d22dMarc Blank */ 1905c523858385176c33a7456bb84035de78552d22dMarc Blank private static class LocalMessageInfo { 1915c523858385176c33a7456bb84035de78552d22dMarc Blank private static final int COLUMN_ID = 0; 1925c523858385176c33a7456bb84035de78552d22dMarc Blank private static final int COLUMN_FLAG_READ = 1; 1935c523858385176c33a7456bb84035de78552d22dMarc Blank private static final int COLUMN_FLAG_FAVORITE = 2; 1945c523858385176c33a7456bb84035de78552d22dMarc Blank private static final int COLUMN_FLAG_LOADED = 3; 1955c523858385176c33a7456bb84035de78552d22dMarc Blank private static final int COLUMN_SERVER_ID = 4; 19637b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon private static final int COLUMN_FLAGS = 5; 19737b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon private static final int COLUMN_TIMESTAMP = 6; 1985c523858385176c33a7456bb84035de78552d22dMarc Blank private static final String[] PROJECTION = new String[] { 19937b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon EmailContent.RECORD_ID, MessageColumns.FLAG_READ, MessageColumns.FLAG_FAVORITE, 20037b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon MessageColumns.FLAG_LOADED, SyncColumns.SERVER_ID, MessageColumns.FLAGS, 20137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon MessageColumns.TIMESTAMP 2025c523858385176c33a7456bb84035de78552d22dMarc Blank }; 2035c523858385176c33a7456bb84035de78552d22dMarc Blank 2045c523858385176c33a7456bb84035de78552d22dMarc Blank final long mId; 2055c523858385176c33a7456bb84035de78552d22dMarc Blank final boolean mFlagRead; 2065c523858385176c33a7456bb84035de78552d22dMarc Blank final boolean mFlagFavorite; 2075c523858385176c33a7456bb84035de78552d22dMarc Blank final int mFlagLoaded; 2085c523858385176c33a7456bb84035de78552d22dMarc Blank final String mServerId; 2095c523858385176c33a7456bb84035de78552d22dMarc Blank final int mFlags; 21066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon final long mTimestamp; 2115c523858385176c33a7456bb84035de78552d22dMarc Blank 2125c523858385176c33a7456bb84035de78552d22dMarc Blank public LocalMessageInfo(Cursor c) { 2135c523858385176c33a7456bb84035de78552d22dMarc Blank mId = c.getLong(COLUMN_ID); 2145c523858385176c33a7456bb84035de78552d22dMarc Blank mFlagRead = c.getInt(COLUMN_FLAG_READ) != 0; 2155c523858385176c33a7456bb84035de78552d22dMarc Blank mFlagFavorite = c.getInt(COLUMN_FLAG_FAVORITE) != 0; 2165c523858385176c33a7456bb84035de78552d22dMarc Blank mFlagLoaded = c.getInt(COLUMN_FLAG_LOADED); 2175c523858385176c33a7456bb84035de78552d22dMarc Blank mServerId = c.getString(COLUMN_SERVER_ID); 2185c523858385176c33a7456bb84035de78552d22dMarc Blank mFlags = c.getInt(COLUMN_FLAGS); 21966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon mTimestamp = c.getLong(COLUMN_TIMESTAMP); 2205c523858385176c33a7456bb84035de78552d22dMarc Blank // Note: mailbox key and account key not needed - they are projected for the SELECT 2215c523858385176c33a7456bb84035de78552d22dMarc Blank } 2225c523858385176c33a7456bb84035de78552d22dMarc Blank } 2235c523858385176c33a7456bb84035de78552d22dMarc Blank 22425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static class OldestTimestampInfo { 22525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final int COLUMN_OLDEST_TIMESTAMP = 0; 22625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon private static final String[] PROJECTION = new String[] { 22725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon "MIN(" + MessageColumns.TIMESTAMP + ")" 22825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon }; 22925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 23025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 2315c523858385176c33a7456bb84035de78552d22dMarc Blank /** 2325c523858385176c33a7456bb84035de78552d22dMarc Blank * Load the structure and body of messages not yet synced 2335c523858385176c33a7456bb84035de78552d22dMarc Blank * @param account the account we're syncing 2345c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteFolder the (open) Folder we're working on 2357d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler * @param messages an array of Messages we've got headers for 2365c523858385176c33a7456bb84035de78552d22dMarc Blank * @param toMailbox the destination mailbox we're syncing 2375c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 2385c523858385176c33a7456bb84035de78552d22dMarc Blank */ 2395c523858385176c33a7456bb84035de78552d22dMarc Blank static void loadUnsyncedMessages(final Context context, final Account account, 2405c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteFolder, ArrayList<Message> messages, final Mailbox toMailbox) 2415c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 2425c523858385176c33a7456bb84035de78552d22dMarc Blank 2435c523858385176c33a7456bb84035de78552d22dMarc Blank FetchProfile fp = new FetchProfile(); 2445c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.STRUCTURE); 2455c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.fetch(messages.toArray(new Message[messages.size()]), fp, null); 2461a0aa22dc02fdfc6876ee90926acc447da8fe935Martin Hibdon Message [] oneMessageArray = new Message[1]; 2475c523858385176c33a7456bb84035de78552d22dMarc Blank for (Message message : messages) { 2485c523858385176c33a7456bb84035de78552d22dMarc Blank // Build a list of parts we are interested in. Text parts will be downloaded 2495c523858385176c33a7456bb84035de78552d22dMarc Blank // right now, attachments will be left for later. 2505c523858385176c33a7456bb84035de78552d22dMarc Blank ArrayList<Part> viewables = new ArrayList<Part>(); 2515c523858385176c33a7456bb84035de78552d22dMarc Blank ArrayList<Part> attachments = new ArrayList<Part>(); 2525c523858385176c33a7456bb84035de78552d22dMarc Blank MimeUtility.collectParts(message, viewables, attachments); 2535c523858385176c33a7456bb84035de78552d22dMarc Blank // Download the viewables immediately 2541a0aa22dc02fdfc6876ee90926acc447da8fe935Martin Hibdon oneMessageArray[0] = message; 2555c523858385176c33a7456bb84035de78552d22dMarc Blank for (Part part : viewables) { 2565c523858385176c33a7456bb84035de78552d22dMarc Blank fp.clear(); 2575c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(part); 2581a0aa22dc02fdfc6876ee90926acc447da8fe935Martin Hibdon remoteFolder.fetch(oneMessageArray, fp, null); 2595c523858385176c33a7456bb84035de78552d22dMarc Blank } 2605c523858385176c33a7456bb84035de78552d22dMarc Blank // Store the updated message locally and mark it fully loaded 2615c523858385176c33a7456bb84035de78552d22dMarc Blank Utilities.copyOneMessageToProvider(context, message, account, toMailbox, 2625c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.FLAG_LOADED_COMPLETE); 2635c523858385176c33a7456bb84035de78552d22dMarc Blank } 2645c523858385176c33a7456bb84035de78552d22dMarc Blank } 2655c523858385176c33a7456bb84035de78552d22dMarc Blank 2665c523858385176c33a7456bb84035de78552d22dMarc Blank public static void downloadFlagAndEnvelope(final Context context, final Account account, 2675c523858385176c33a7456bb84035de78552d22dMarc Blank final Mailbox mailbox, Folder remoteFolder, ArrayList<Message> unsyncedMessages, 2685c523858385176c33a7456bb84035de78552d22dMarc Blank HashMap<String, LocalMessageInfo> localMessageMap, final ArrayList<Long> unseenMessages) 2695c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 2705c523858385176c33a7456bb84035de78552d22dMarc Blank FetchProfile fp = new FetchProfile(); 2715c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.FLAGS); 2725c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.ENVELOPE); 2735c523858385176c33a7456bb84035de78552d22dMarc Blank 2745c523858385176c33a7456bb84035de78552d22dMarc Blank final HashMap<String, LocalMessageInfo> localMapCopy; 2755c523858385176c33a7456bb84035de78552d22dMarc Blank if (localMessageMap != null) 2765c523858385176c33a7456bb84035de78552d22dMarc Blank localMapCopy = new HashMap<String, LocalMessageInfo>(localMessageMap); 2775c523858385176c33a7456bb84035de78552d22dMarc Blank else { 2785c523858385176c33a7456bb84035de78552d22dMarc Blank localMapCopy = new HashMap<String, LocalMessageInfo>(); 2795c523858385176c33a7456bb84035de78552d22dMarc Blank } 2805c523858385176c33a7456bb84035de78552d22dMarc Blank 2817d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler remoteFolder.fetch(unsyncedMessages.toArray(new Message[unsyncedMessages.size()]), fp, 2825c523858385176c33a7456bb84035de78552d22dMarc Blank new MessageRetrievalListener() { 2835c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 2845c523858385176c33a7456bb84035de78552d22dMarc Blank public void messageRetrieved(Message message) { 2855c523858385176c33a7456bb84035de78552d22dMarc Blank try { 2865c523858385176c33a7456bb84035de78552d22dMarc Blank // Determine if the new message was already known (e.g. partial) 2875c523858385176c33a7456bb84035de78552d22dMarc Blank // And create or reload the full message info 2885c523858385176c33a7456bb84035de78552d22dMarc Blank LocalMessageInfo localMessageInfo = 2895c523858385176c33a7456bb84035de78552d22dMarc Blank localMapCopy.get(message.getUid()); 2907d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler EmailContent.Message localMessage; 2915c523858385176c33a7456bb84035de78552d22dMarc Blank if (localMessageInfo == null) { 2925c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage = new EmailContent.Message(); 2935c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 2945c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage = EmailContent.Message.restoreMessageWithId( 2955c523858385176c33a7456bb84035de78552d22dMarc Blank context, localMessageInfo.mId); 2965c523858385176c33a7456bb84035de78552d22dMarc Blank } 2975c523858385176c33a7456bb84035de78552d22dMarc Blank 2985c523858385176c33a7456bb84035de78552d22dMarc Blank if (localMessage != null) { 2995c523858385176c33a7456bb84035de78552d22dMarc Blank try { 3005c523858385176c33a7456bb84035de78552d22dMarc Blank // Copy the fields that are available into the message 3015c523858385176c33a7456bb84035de78552d22dMarc Blank LegacyConversions.updateMessageFields(localMessage, 3025c523858385176c33a7456bb84035de78552d22dMarc Blank message, account.mId, mailbox.mId); 3035c523858385176c33a7456bb84035de78552d22dMarc Blank // Commit the message to the local store 3045c523858385176c33a7456bb84035de78552d22dMarc Blank Utilities.saveOrUpdate(localMessage, context); 3055c523858385176c33a7456bb84035de78552d22dMarc Blank // Track the "new" ness of the downloaded message 3065c523858385176c33a7456bb84035de78552d22dMarc Blank if (!message.isSet(Flag.SEEN) && unseenMessages != null) { 3075c523858385176c33a7456bb84035de78552d22dMarc Blank unseenMessages.add(localMessage.mId); 3085c523858385176c33a7456bb84035de78552d22dMarc Blank } 3095c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 310560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 3115c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while copying downloaded message." + me); 3125c523858385176c33a7456bb84035de78552d22dMarc Blank } 3135c523858385176c33a7456bb84035de78552d22dMarc Blank } 3145c523858385176c33a7456bb84035de78552d22dMarc Blank } 3155c523858385176c33a7456bb84035de78552d22dMarc Blank catch (Exception e) { 316560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 3175c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while storing downloaded message." + e.toString()); 3185c523858385176c33a7456bb84035de78552d22dMarc Blank } 3195c523858385176c33a7456bb84035de78552d22dMarc Blank } 3205c523858385176c33a7456bb84035de78552d22dMarc Blank 3215c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 3225c523858385176c33a7456bb84035de78552d22dMarc Blank public void loadAttachmentProgress(int progress) { 3235c523858385176c33a7456bb84035de78552d22dMarc Blank } 3245c523858385176c33a7456bb84035de78552d22dMarc Blank }); 3255c523858385176c33a7456bb84035de78552d22dMarc Blank 3265c523858385176c33a7456bb84035de78552d22dMarc Blank } 3275c523858385176c33a7456bb84035de78552d22dMarc Blank 3285c523858385176c33a7456bb84035de78552d22dMarc Blank /** 3295c523858385176c33a7456bb84035de78552d22dMarc Blank * Synchronizer for IMAP. 3305c523858385176c33a7456bb84035de78552d22dMarc Blank * 3315c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO Break this method up into smaller chunks. 3325c523858385176c33a7456bb84035de78552d22dMarc Blank * 3335c523858385176c33a7456bb84035de78552d22dMarc Blank * @param account the account to sync 3345c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the mailbox to sync 335c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * @param loadMore whether we should be loading more older messages 336c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * @param uiRefresh whether this request is in response to a user action 3375c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 3385c523858385176c33a7456bb84035de78552d22dMarc Blank */ 3397d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private synchronized static void synchronizeMailboxGeneric(final Context context, 3407d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final Account account, final Mailbox mailbox, final boolean loadMore, 3417d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final boolean uiRefresh) 342c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon throws MessagingException { 343c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 344466eb2dcd2660bf0e5d8d1440db598a30ca184f3Martin Hibdon LogUtils.d(Logging.LOG_TAG, "synchronizeMailboxGeneric " + account + " " + mailbox + " " 345c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + loadMore + " " + uiRefresh); 34625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 3475c523858385176c33a7456bb84035de78552d22dMarc Blank final ArrayList<Long> unseenMessages = new ArrayList<Long>(); 3485c523858385176c33a7456bb84035de78552d22dMarc Blank 3495c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 3505c523858385176c33a7456bb84035de78552d22dMarc Blank 351c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 0. We do not ever sync DRAFTS or OUTBOX (down or up) 3525c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) { 3535c523858385176c33a7456bb84035de78552d22dMarc Blank return; 3545c523858385176c33a7456bb84035de78552d22dMarc Blank } 3555c523858385176c33a7456bb84035de78552d22dMarc Blank 356c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 1. Figure out what our sync window should be. 357c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long endDate; 358c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 359c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // We will do a full sync if the user has actively requested a sync, or if it has been 360c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // too long since the last full sync. 361c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If we have rebooted since the last full sync, then we may get a negative 362c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // timeSinceLastFullSync. In this case, we don't know how long it's been since the last 363c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // full sync so we should perform the full sync. 364c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon final long timeSinceLastFullSync = SystemClock.elapsedRealtime() - 365c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon mailbox.mLastFullSyncTime; 366c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon final boolean fullSync = (uiRefresh || loadMore || 367c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon timeSinceLastFullSync >= FULL_SYNC_INTERVAL_MILLIS || timeSinceLastFullSync < 0); 368c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 369c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync) { 370c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // Find the oldest message in the local store. We need our time window to include 371c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // all messages that are currently present locally. 372c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = System.currentTimeMillis() - FULL_SYNC_WINDOW_MILLIS; 373c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Cursor localOldestCursor = null; 374c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon try { 375802bf1b4a7e36caa84d790198abc8421873b821cAlon Albert // b/11520812 Ignore message with timestamp = 0 (which includes NULL) 376c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localOldestCursor = resolver.query(EmailContent.Message.CONTENT_URI, 377c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon OldestTimestampInfo.PROJECTION, 378c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + " AND " + 379802bf1b4a7e36caa84d790198abc8421873b821cAlon Albert MessageColumns.MAILBOX_KEY + "=? AND " + 380802bf1b4a7e36caa84d790198abc8421873b821cAlon Albert MessageColumns.TIMESTAMP + "!=0", 381c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] {String.valueOf(account.mId), String.valueOf(mailbox.mId)}, 382c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon null); 383c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localOldestCursor != null && localOldestCursor.moveToFirst()) { 384c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long oldestLocalMessageDate = localOldestCursor.getLong( 385c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon OldestTimestampInfo.COLUMN_OLDEST_TIMESTAMP); 386c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (oldestLocalMessageDate > 0) { 387c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = Math.min(endDate, oldestLocalMessageDate); 388c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d( 389c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Logging.LOG_TAG, "oldest local message " + oldestLocalMessageDate); 390c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 391c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 392c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } finally { 393c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localOldestCursor != null) { 394c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localOldestCursor.close(); 395c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 3965c523858385176c33a7456bb84035de78552d22dMarc Blank } 397c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "full sync: original window: now - " + endDate); 398c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } else { 399c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // We are doing a frequent, quick sync. This only syncs a small time window, so that 400c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // we wil get any new messages, but not spend a lot of bandwidth downloading 401c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // messageIds that we most likely already have. 402c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = System.currentTimeMillis() - QUICK_SYNC_WINDOW_MILLIS; 403c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "quick sync: original window: now - " + endDate); 4045c523858385176c33a7456bb84035de78552d22dMarc Blank } 4055c523858385176c33a7456bb84035de78552d22dMarc Blank 406c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 2. Open the remote folder and create the remote folder if necessary 4075c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = Store.getInstance(account, context); 4085c523858385176c33a7456bb84035de78552d22dMarc Blank // The account might have been deleted 40935cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon if (remoteStore == null) { 41035cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon LogUtils.d(Logging.LOG_TAG, "account is apparently deleted"); 41135cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon return; 41235cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon } 413d1a87bc02d65dde9e635848531e09aadc79ff538Paul Westbrook final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 4145c523858385176c33a7456bb84035de78552d22dMarc Blank 415c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If the folder is a "special" folder we need to see if it exists 416c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // on the remote server. It if does not exist we'll try to create it. If we 417c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // can't create we'll abort. This will happen on every single Pop3 folder as 418c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // designed and on Imap folders during error conditions. This allows us 419c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // to treat Pop3 and Imap the same in this code. 420c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT) { 4215c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 4225c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { 42335cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon LogUtils.w(Logging.LOG_TAG, "could not create remote folder type %d", 42435cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon mailbox.mType); 4255c523858385176c33a7456bb84035de78552d22dMarc Blank return; 4265c523858385176c33a7456bb84035de78552d22dMarc Blank } 4275c523858385176c33a7456bb84035de78552d22dMarc Blank } 4285c523858385176c33a7456bb84035de78552d22dMarc Blank } 4295c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 4305c523858385176c33a7456bb84035de78552d22dMarc Blank 431c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 3. Trash any remote messages that are marked as trashed locally. 4325c523858385176c33a7456bb84035de78552d22dMarc Blank // TODO - this comment was here, but no code was here. 4335c523858385176c33a7456bb84035de78552d22dMarc Blank 434c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 4. Get the number of messages on the server. 43517d5bbf768c27ac7782b155e2ab25bcd480f5dcfYu Ping Hu final int remoteMessageCount = remoteFolder.getMessageCount(); 43666eef4565dd0a8b99e927bed7b64c472d24c276dYu Ping Hu 437c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 5. Save folder message count locally. 43825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon mailbox.updateMessageCount(context, remoteMessageCount); 4395c523858385176c33a7456bb84035de78552d22dMarc Blank 440c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 6. Get all message Ids in our sync window: 44125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] remoteMessages; 442c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon remoteMessages = remoteFolder.getMessages(0, endDate, null); 443c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "received " + remoteMessages.length + " messages"); 44425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 445c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 7. See if we need any additional messages beyond our date query range results. 44625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // If we do, keep increasing the size of our query window until we have 44725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // enough, or until we have all messages in the mailbox. 448c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon int totalCountNeeded; 44925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (loadMore) { 450c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = remoteMessages.length + LOAD_MORE_MIN_INCREMENT; 451c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } else { 452c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = remoteMessages.length; 453c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync && totalCountNeeded < MINIMUM_MESSAGES_TO_SYNC) { 454c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = MINIMUM_MESSAGES_TO_SYNC; 455c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 45625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 45725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "need " + totalCountNeeded + " total"); 45825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 45925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final int additionalMessagesNeeded = totalCountNeeded - remoteMessages.length; 46025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (additionalMessagesNeeded > 0) { 46125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "trying to get " + additionalMessagesNeeded + " more"); 462c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long startDate = endDate - 1; 46325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] additionalMessages = new Message[0]; 46425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon long windowIncreaseSize = INITIAL_WINDOW_SIZE_INCREASE; 465c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon while (additionalMessages.length < additionalMessagesNeeded && endDate > 0) { 46625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon endDate = endDate - windowIncreaseSize; 467fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon if (endDate < 0) { 468fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon LogUtils.d(Logging.LOG_TAG, "window size too large, this is the last attempt"); 469fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon endDate = 0; 470fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon } 471c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, 472c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon "requesting additional messages from range " + startDate + " - " + endDate); 47325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon additionalMessages = remoteFolder.getMessages(startDate, endDate, null); 47425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 47525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // If don't get enough messages with the first window size expansion, 47625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // we need to accelerate rate at which the window expands. Otherwise, 47725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // if there were no messages for several weeks, we'd always end up 47825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // performing dozens of queries. 47925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon windowIncreaseSize *= 2; 4805c523858385176c33a7456bb84035de78552d22dMarc Blank } 4815c523858385176c33a7456bb84035de78552d22dMarc Blank 48225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "additionalMessages " + additionalMessages.length); 483fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon if (additionalMessages.length < additionalMessagesNeeded) { 484fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // We have attempted to load a window that goes all the way back to time zero, 485fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // but we still don't have as many messages as the server says are in the inbox. 486fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // This is not expected to happen. 487c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.e(Logging.LOG_TAG, "expected to find " + additionalMessagesNeeded 488c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " more messages, only got " + additionalMessages.length); 489fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon } 49025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon int additionalToKeep = additionalMessages.length; 49125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (additionalMessages.length > LOAD_MORE_MAX_INCREMENT) { 49225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // We have way more additional messages than intended, drop some of them. 49325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // The last messages are the most recent, so those are the ones we need to keep. 49425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon additionalToKeep = LOAD_MORE_MAX_INCREMENT; 4955c523858385176c33a7456bb84035de78552d22dMarc Blank } 49625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 49725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Copy the messages into one array. 49825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] allMessages = new Message[remoteMessages.length + additionalToKeep]; 49925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon System.arraycopy(remoteMessages, 0, allMessages, 0, remoteMessages.length); 50025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // additionalMessages may have more than we need, only copy the last 501c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // several. These are the most recent messages in that set because 502c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // of the way IMAP server returns messages. 50325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon System.arraycopy(additionalMessages, additionalMessages.length - additionalToKeep, 50425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon allMessages, remoteMessages.length, additionalToKeep); 50525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon remoteMessages = allMessages; 5065c523858385176c33a7456bb84035de78552d22dMarc Blank } 5075c523858385176c33a7456bb84035de78552d22dMarc Blank 508c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 8. Get the all of the local messages within the sync window, and create 509c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // an index of the uids. 51066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // The IMAP query for messages ignores time, and only looks at the date part of the endDate. 51166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // So if we query for messages since Aug 11 at 3:00 PM, we can get messages from any time 51266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // on Aug 11. Our IMAP query results can include messages up to 24 hours older than endDate, 51366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // or up to 25 hours older at a daylight savings transition. 51466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // It is important that we have the Id of any local message that could potentially be 51566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // returned by the IMAP query, or we will create duplicate copies of the same messages. 51666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // So we will increase our local query range by this much. 51766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Note that this complicates deletion: It's not okay to delete anything that is in the 51866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // localMessageMap but not in the remote result, because we know that we may be getting 51966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Ids of local messages that are outside the IMAP query window. 520c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Cursor localUidCursor = null; 521c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>(); 522c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon try { 5230c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // FLAG: There is a problem that causes us to store the wrong date on some messages, 5240c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // so messages get a date of zero. If we filter these messages out and don't put them 5250c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // in our localMessageMap, then we'll end up loading the same message again. 5260c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // See b/10508861 5270c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon// final long queryEndDate = endDate - DateUtils.DAY_IN_MILLIS - DateUtils.HOUR_IN_MILLIS; 5280c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon final long queryEndDate = 0; 529c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localUidCursor = resolver.query( 530c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.CONTENT_URI, 531c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LocalMessageInfo.PROJECTION, 532c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.MessageColumns.ACCOUNT_KEY + "=?" 533c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " AND " + MessageColumns.MAILBOX_KEY + "=?" 534c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " AND " + MessageColumns.TIMESTAMP + ">=?", 535c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] { 536c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon String.valueOf(account.mId), 537c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon String.valueOf(mailbox.mId), 53866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon String.valueOf(queryEndDate) }, 539c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon null); 540c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon while (localUidCursor.moveToNext()) { 541c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LocalMessageInfo info = new LocalMessageInfo(localUidCursor); 542c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If the message has no server id, it's local only. This should only happen for 543c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // mail created on the client that has failed to upsync. We want to ignore such 544c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // mail during synchronization (i.e. leave it as-is and let the next sync try again 545c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // to upsync). 546c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (!TextUtils.isEmpty(info.mServerId)) { 547c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localMessageMap.put(info.mServerId, info); 548c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 549c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 550c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } finally { 551c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localUidCursor != null) { 552c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localUidCursor.close(); 553c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 554c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 555c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 556c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 9. Get a list of the messages that are in the remote list but not on the 557c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // local store, or messages that are in the local store but failed to download 558c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // on the last sync. These are the new messages that we will download. 559c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // Note, we also skip syncing messages which are flagged as "deleted message" sentinels, 560c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // because they are locally deleted and we don't need or want the old message from 561c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // the server. 56225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final ArrayList<Message> unsyncedMessages = new ArrayList<Message>(); 56325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final HashMap<String, Message> remoteUidMap = new HashMap<String, Message>(); 56425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Process the messages in the reverse order we received them in. This means that 565c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // we load the most recent one first, which gives a better user experience. 56625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon for (int i = remoteMessages.length - 1; i >= 0; i--) { 56725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message message = remoteMessages[i]; 56825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "remote message " + message.getUid()); 56925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon remoteUidMap.put(message.getUid(), message); 57025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 57125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LocalMessageInfo localMessage = localMessageMap.get(message.getUid()); 57225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 57325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // localMessage == null -> message has never been created (not even headers) 57425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = UNLOADED -> message created, but none of body loaded 57525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded 57625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = COMPLETE -> message body has been completely loaded 57725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = DELETED -> message has been deleted 57825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Only the first two of these are "unsynced", so let's retrieve them 57925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (localMessage == null || 58025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED) || 58125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_PARTIAL)) { 58225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon unsyncedMessages.add(message); 58325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 58425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 58525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 586c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 10. Download basic info about the new/unloaded messages (if any) 5875c523858385176c33a7456bb84035de78552d22dMarc Blank /* 5885c523858385176c33a7456bb84035de78552d22dMarc Blank * Fetch the flags and envelope only of the new messages. This is intended to get us 5895c523858385176c33a7456bb84035de78552d22dMarc Blank * critical data as fast as possible, and then we'll fill in the details. 5905c523858385176c33a7456bb84035de78552d22dMarc Blank */ 5915c523858385176c33a7456bb84035de78552d22dMarc Blank if (unsyncedMessages.size() > 0) { 5925c523858385176c33a7456bb84035de78552d22dMarc Blank downloadFlagAndEnvelope(context, account, mailbox, remoteFolder, unsyncedMessages, 5935c523858385176c33a7456bb84035de78552d22dMarc Blank localMessageMap, unseenMessages); 5945c523858385176c33a7456bb84035de78552d22dMarc Blank } 5955c523858385176c33a7456bb84035de78552d22dMarc Blank 596d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // 11. Refresh the flags for any messages in the local store that we didn't just download. 597d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // TODO This is a bit wasteful because we're also updating any messages we already did get 598d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // the flags and envelope for previously. 5995c523858385176c33a7456bb84035de78552d22dMarc Blank FetchProfile fp = new FetchProfile(); 6005c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.FLAGS); 6015c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.fetch(remoteMessages, fp, null); 6025c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsSeen = false; 6035c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsFlagged = false; 6045c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsAnswered = false; 6055c523858385176c33a7456bb84035de78552d22dMarc Blank for (Flag flag : remoteFolder.getPermanentFlags()) { 6065c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.SEEN) { 6075c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsSeen = true; 6085c523858385176c33a7456bb84035de78552d22dMarc Blank } 6095c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.FLAGGED) { 6105c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsFlagged = true; 6115c523858385176c33a7456bb84035de78552d22dMarc Blank } 6125c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.ANSWERED) { 6135c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsAnswered = true; 6145c523858385176c33a7456bb84035de78552d22dMarc Blank } 6155c523858385176c33a7456bb84035de78552d22dMarc Blank } 61625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 617c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 12. Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3) 6185c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) { 6195c523858385176c33a7456bb84035de78552d22dMarc Blank for (Message remoteMessage : remoteMessages) { 6205c523858385176c33a7456bb84035de78552d22dMarc Blank LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid()); 6215c523858385176c33a7456bb84035de78552d22dMarc Blank if (localMessageInfo == null) { 6225c523858385176c33a7456bb84035de78552d22dMarc Blank continue; 6235c523858385176c33a7456bb84035de78552d22dMarc Blank } 6245c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localSeen = localMessageInfo.mFlagRead; 6255c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSeen = remoteMessage.isSet(Flag.SEEN); 6265c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen)); 6275c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localFlagged = localMessageInfo.mFlagFavorite; 6285c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED); 6295c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged)); 6305c523858385176c33a7456bb84035de78552d22dMarc Blank int localFlags = localMessageInfo.mFlags; 6315c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0; 6325c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED); 6335c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered)); 6345c523858385176c33a7456bb84035de78552d22dMarc Blank if (newSeen || newFlagged || newAnswered) { 6355c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId( 6365c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_URI, localMessageInfo.mId); 6375c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues updateValues = new ContentValues(); 6385c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAG_READ, remoteSeen); 6395c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged); 6405c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteAnswered) { 6415c523858385176c33a7456bb84035de78552d22dMarc Blank localFlags |= EmailContent.Message.FLAG_REPLIED_TO; 6425c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 6435c523858385176c33a7456bb84035de78552d22dMarc Blank localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO; 6445c523858385176c33a7456bb84035de78552d22dMarc Blank } 6455c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAGS, localFlags); 6465c523858385176c33a7456bb84035de78552d22dMarc Blank resolver.update(uri, updateValues, null, null); 6475c523858385176c33a7456bb84035de78552d22dMarc Blank } 6485c523858385176c33a7456bb84035de78552d22dMarc Blank } 6495c523858385176c33a7456bb84035de78552d22dMarc Blank } 6505c523858385176c33a7456bb84035de78552d22dMarc Blank 651c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 13. Remove messages that are in the local store and in the current sync window, 65266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // but no longer on the remote store. Note that localMessageMap can contain messages 65366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // that are not actually in our sync window. We need to check the timestamp to ensure 65466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // that it is before deleting. 65566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon for (final LocalMessageInfo info : localMessageMap.values()) { 65666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // If this message is inside our sync window, and we cannot find it in our list 65766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // of remote messages, then we know it's been deleted from the server. 65866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon if (info.mTimestamp >= endDate && !remoteUidMap.containsKey(info.mServerId)) { 65966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete associated data (attachment files) 66066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Attachment & Body records are auto-deleted when we delete the Message record 66166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, info.mId); 66266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon 66366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete the message itself 66466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri uriToDelete = ContentUris.withAppendedId( 66566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.CONTENT_URI, info.mId); 66666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(uriToDelete, null, null); 66766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon 66866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete extra rows (e.g. synced or deleted) 66966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri syncRowToDelete = ContentUris.withAppendedId( 67066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.UPDATED_CONTENT_URI, info.mId); 67166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(syncRowToDelete, null, null); 67266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri deletERowToDelete = ContentUris.withAppendedId( 67366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.UPDATED_CONTENT_URI, info.mId); 67466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(deletERowToDelete, null, null); 67566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon } 6765c523858385176c33a7456bb84035de78552d22dMarc Blank } 6775c523858385176c33a7456bb84035de78552d22dMarc Blank 6785c523858385176c33a7456bb84035de78552d22dMarc Blank loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox); 6795c523858385176c33a7456bb84035de78552d22dMarc Blank 680c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync) { 681c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon mailbox.updateLastFullSyncTime(context, SystemClock.elapsedRealtime()); 682c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 683c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 684c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 14. Clean up and report results 6855c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 6865c523858385176c33a7456bb84035de78552d22dMarc Blank } 6875c523858385176c33a7456bb84035de78552d22dMarc Blank 6885c523858385176c33a7456bb84035de78552d22dMarc Blank /** 6895c523858385176c33a7456bb84035de78552d22dMarc Blank * Find messages in the updated table that need to be written back to server. 6905c523858385176c33a7456bb84035de78552d22dMarc Blank * 6915c523858385176c33a7456bb84035de78552d22dMarc Blank * Handles: 6925c523858385176c33a7456bb84035de78552d22dMarc Blank * Read/Unread 6935c523858385176c33a7456bb84035de78552d22dMarc Blank * Flagged 6945c523858385176c33a7456bb84035de78552d22dMarc Blank * Append (upload) 6955c523858385176c33a7456bb84035de78552d22dMarc Blank * Move To Trash 6965c523858385176c33a7456bb84035de78552d22dMarc Blank * Empty trash 6975c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO: 6985c523858385176c33a7456bb84035de78552d22dMarc Blank * Move 6995c523858385176c33a7456bb84035de78552d22dMarc Blank * 7005c523858385176c33a7456bb84035de78552d22dMarc Blank * @param account the account to scan for pending actions 7015c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 7025c523858385176c33a7456bb84035de78552d22dMarc Blank */ 7035c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingActionsSynchronous(Context context, Account account) 704c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon throws MessagingException { 7055c523858385176c33a7456bb84035de78552d22dMarc Blank TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account)); 7065c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs = new String[] { Long.toString(account.mId) }; 7075c523858385176c33a7456bb84035de78552d22dMarc Blank 7085c523858385176c33a7456bb84035de78552d22dMarc Blank // Handle deletes first, it's always better to get rid of things first 7095c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingDeletesSynchronous(context, account, accountIdArgs); 7105c523858385176c33a7456bb84035de78552d22dMarc Blank 7115c523858385176c33a7456bb84035de78552d22dMarc Blank // Handle uploads (currently, only to sent messages) 7125c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingUploadsSynchronous(context, account, accountIdArgs); 7135c523858385176c33a7456bb84035de78552d22dMarc Blank 7145c523858385176c33a7456bb84035de78552d22dMarc Blank // Now handle updates / upsyncs 7155c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingUpdatesSynchronous(context, account, accountIdArgs); 7165c523858385176c33a7456bb84035de78552d22dMarc Blank } 7175c523858385176c33a7456bb84035de78552d22dMarc Blank 7185c523858385176c33a7456bb84035de78552d22dMarc Blank /** 7195c523858385176c33a7456bb84035de78552d22dMarc Blank * Get the mailbox corresponding to the remote location of a message; this will normally be 7205c523858385176c33a7456bb84035de78552d22dMarc Blank * the mailbox whose _id is mailboxKey, except for search results, where we must look it up 721c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * by serverId. 722c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * 7235c523858385176c33a7456bb84035de78552d22dMarc Blank * @param message the message in question 7245c523858385176c33a7456bb84035de78552d22dMarc Blank * @return the mailbox in which the message resides on the server 7255c523858385176c33a7456bb84035de78552d22dMarc Blank */ 726c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon private static Mailbox getRemoteMailboxForMessage( 727c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Context context, EmailContent.Message message) { 7285c523858385176c33a7456bb84035de78552d22dMarc Blank // If this is a search result, use the protocolSearchInfo field to get the server info 7295c523858385176c33a7456bb84035de78552d22dMarc Blank if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) { 7305c523858385176c33a7456bb84035de78552d22dMarc Blank long accountKey = message.mAccountKey; 7315c523858385176c33a7456bb84035de78552d22dMarc Blank String protocolSearchInfo = message.mProtocolSearchInfo; 7325c523858385176c33a7456bb84035de78552d22dMarc Blank if (accountKey == mLastSearchAccountKey && 7335c523858385176c33a7456bb84035de78552d22dMarc Blank protocolSearchInfo.equals(mLastSearchServerId)) { 7345c523858385176c33a7456bb84035de78552d22dMarc Blank return mLastSearchRemoteMailbox; 7355c523858385176c33a7456bb84035de78552d22dMarc Blank } 73617d5bbf768c27ac7782b155e2ab25bcd480f5dcfYu Ping Hu Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, 7375c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION, 738c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] {protocolSearchInfo, Long.toString(accountKey) }, 7395c523858385176c33a7456bb84035de78552d22dMarc Blank null); 7405c523858385176c33a7456bb84035de78552d22dMarc Blank try { 7415c523858385176c33a7456bb84035de78552d22dMarc Blank if (c.moveToNext()) { 7425c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = new Mailbox(); 7435c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox.restore(c); 7445c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchAccountKey = accountKey; 7455c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchServerId = protocolSearchInfo; 7465c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchRemoteMailbox = mailbox; 7475c523858385176c33a7456bb84035de78552d22dMarc Blank return mailbox; 7485c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 7495c523858385176c33a7456bb84035de78552d22dMarc Blank return null; 7505c523858385176c33a7456bb84035de78552d22dMarc Blank } 7515c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 7525c523858385176c33a7456bb84035de78552d22dMarc Blank c.close(); 7535c523858385176c33a7456bb84035de78552d22dMarc Blank } 7545c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 7555c523858385176c33a7456bb84035de78552d22dMarc Blank return Mailbox.restoreMailboxWithId(context, message.mMailboxKey); 7565c523858385176c33a7456bb84035de78552d22dMarc Blank } 7575c523858385176c33a7456bb84035de78552d22dMarc Blank } 7585c523858385176c33a7456bb84035de78552d22dMarc Blank 7595c523858385176c33a7456bb84035de78552d22dMarc Blank /** 7605c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in the Message_Deletes table, look for differences that 7615c523858385176c33a7456bb84035de78552d22dMarc Blank * we can deal with, and do the work. 7625c523858385176c33a7456bb84035de78552d22dMarc Blank */ 7635c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingDeletesSynchronous(Context context, Account account, 7645c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 7655c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor deletes = context.getContentResolver().query( 7665c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.DELETED_CONTENT_URI, 7675c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_PROJECTION, 7685c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs, 7695c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.MAILBOX_KEY); 7705c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 7715c523858385176c33a7456bb84035de78552d22dMarc Blank try { 7725c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 7735c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 7745c523858385176c33a7456bb84035de78552d22dMarc Blank // loop through messages marked as deleted 7755c523858385176c33a7456bb84035de78552d22dMarc Blank while (deletes.moveToNext()) { 7765c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message oldMessage = 7775c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.getContent(deletes, EmailContent.Message.class); 7785c523858385176c33a7456bb84035de78552d22dMarc Blank 7795c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMessage != null) { 7805c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = oldMessage.mId; 7815c523858385176c33a7456bb84035de78552d22dMarc Blank 7825c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = getRemoteMailboxForMessage(context, oldMessage); 7835c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 7845c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 7855c523858385176c33a7456bb84035de78552d22dMarc Blank } 7867d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final boolean deleteFromTrash = mailbox.mType == Mailbox.TYPE_TRASH; 7875c523858385176c33a7456bb84035de78552d22dMarc Blank 7885c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 7895c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null && deleteFromTrash) { 7905c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 7915c523858385176c33a7456bb84035de78552d22dMarc Blank } 7925c523858385176c33a7456bb84035de78552d22dMarc Blank 7935c523858385176c33a7456bb84035de78552d22dMarc Blank // Dispatch here for specific change types 7945c523858385176c33a7456bb84035de78552d22dMarc Blank if (deleteFromTrash) { 7955c523858385176c33a7456bb84035de78552d22dMarc Blank // Move message to trash 7967d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processPendingDeleteFromTrash(remoteStore, mailbox, oldMessage); 7975c523858385176c33a7456bb84035de78552d22dMarc Blank } 7985c523858385176c33a7456bb84035de78552d22dMarc Blank 7997d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler // Finally, delete the update 8007d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI, 8017d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler oldMessage.mId); 8027d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler context.getContentResolver().delete(uri, null, null); 8037d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler } 8045c523858385176c33a7456bb84035de78552d22dMarc Blank } 8055c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 8065c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 8075c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 8085c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 809560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending delete for id=" 810c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + lastMessageId + ": " + me); 8115c523858385176c33a7456bb84035de78552d22dMarc Blank } 8125c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8135c523858385176c33a7456bb84035de78552d22dMarc Blank deletes.close(); 8145c523858385176c33a7456bb84035de78552d22dMarc Blank } 8155c523858385176c33a7456bb84035de78552d22dMarc Blank } 8165c523858385176c33a7456bb84035de78552d22dMarc Blank 8175c523858385176c33a7456bb84035de78552d22dMarc Blank /** 8185c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in Sent, and are in need of upload, 819c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * and send them to the server. "In need of upload" is defined as: 8205c523858385176c33a7456bb84035de78552d22dMarc Blank * serverId == null (no UID has been assigned) 8215c523858385176c33a7456bb84035de78552d22dMarc Blank * or 8225c523858385176c33a7456bb84035de78552d22dMarc Blank * message is in the updated list 8235c523858385176c33a7456bb84035de78552d22dMarc Blank * 824c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Note we also look for messages that are moving from drafts->outbox->sent. They never 8255c523858385176c33a7456bb84035de78552d22dMarc Blank * go through "drafts" or "outbox" on the server, so we hang onto these until they can be 8265c523858385176c33a7456bb84035de78552d22dMarc Blank * uploaded directly to the Sent folder. 8275c523858385176c33a7456bb84035de78552d22dMarc Blank */ 8285c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingUploadsSynchronous(Context context, Account account, 8295c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 8305c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 8315c523858385176c33a7456bb84035de78552d22dMarc Blank // Find the Sent folder (since that's all we're uploading for now 832c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // TODO: Upsync for all folders? (In case a user moves mail from Sent before it is 833c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // handled. Also, this would generically solve allowing drafts to upload.) 8345c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, 8355c523858385176c33a7456bb84035de78552d22dMarc Blank MailboxColumns.ACCOUNT_KEY + "=?" 8365c523858385176c33a7456bb84035de78552d22dMarc Blank + " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT, 8375c523858385176c33a7456bb84035de78552d22dMarc Blank accountIdArgs, null); 8385c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 8395c523858385176c33a7456bb84035de78552d22dMarc Blank try { 8405c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 8415c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 8425c523858385176c33a7456bb84035de78552d22dMarc Blank while (mailboxes.moveToNext()) { 8435c523858385176c33a7456bb84035de78552d22dMarc Blank long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN); 8445c523858385176c33a7456bb84035de78552d22dMarc Blank String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) }; 8455c523858385176c33a7456bb84035de78552d22dMarc Blank // Demand load mailbox 8465c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = null; 8475c523858385176c33a7456bb84035de78552d22dMarc Blank 8485c523858385176c33a7456bb84035de78552d22dMarc Blank // First handle the "new" messages (serverId == null) 8495c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI, 8505c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.ID_PROJECTION, 8515c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.MAILBOX_KEY + "=?" 8525c523858385176c33a7456bb84035de78552d22dMarc Blank + " and (" + EmailContent.Message.SERVER_ID + " is null" 8535c523858385176c33a7456bb84035de78552d22dMarc Blank + " or " + EmailContent.Message.SERVER_ID + "=''" + ")", 8545c523858385176c33a7456bb84035de78552d22dMarc Blank mailboxKeyArgs, 8555c523858385176c33a7456bb84035de78552d22dMarc Blank null); 8565c523858385176c33a7456bb84035de78552d22dMarc Blank try { 8575c523858385176c33a7456bb84035de78552d22dMarc Blank while (upsyncs1.moveToNext()) { 8585c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 8595c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null) { 8605c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 8615c523858385176c33a7456bb84035de78552d22dMarc Blank } 8625c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the mailbox if it will be needed 8635c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 8645c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 8655c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 8665c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 8675c523858385176c33a7456bb84035de78552d22dMarc Blank } 8685c523858385176c33a7456bb84035de78552d22dMarc Blank } 8695c523858385176c33a7456bb84035de78552d22dMarc Blank // upsync the message 8705c523858385176c33a7456bb84035de78552d22dMarc Blank long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN); 8715c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = id; 8727d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processUploadMessage(context, remoteStore, mailbox, id); 8735c523858385176c33a7456bb84035de78552d22dMarc Blank } 8745c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8755c523858385176c33a7456bb84035de78552d22dMarc Blank if (upsyncs1 != null) { 8765c523858385176c33a7456bb84035de78552d22dMarc Blank upsyncs1.close(); 8775c523858385176c33a7456bb84035de78552d22dMarc Blank } 8785c523858385176c33a7456bb84035de78552d22dMarc Blank } 8795c523858385176c33a7456bb84035de78552d22dMarc Blank } 8805c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 8815c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 8825c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 8835c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 884560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending upsync for id=" 8855c523858385176c33a7456bb84035de78552d22dMarc Blank + lastMessageId + ": " + me); 8865c523858385176c33a7456bb84035de78552d22dMarc Blank } 8875c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8885c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailboxes != null) { 8895c523858385176c33a7456bb84035de78552d22dMarc Blank mailboxes.close(); 8905c523858385176c33a7456bb84035de78552d22dMarc Blank } 8915c523858385176c33a7456bb84035de78552d22dMarc Blank } 8925c523858385176c33a7456bb84035de78552d22dMarc Blank } 8935c523858385176c33a7456bb84035de78552d22dMarc Blank 8945c523858385176c33a7456bb84035de78552d22dMarc Blank /** 8955c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in the Message_Updates table, look for differences that 8965c523858385176c33a7456bb84035de78552d22dMarc Blank * we can deal with, and do the work. 8975c523858385176c33a7456bb84035de78552d22dMarc Blank */ 8985c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingUpdatesSynchronous(Context context, Account account, 8995c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 9005c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 9015c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor updates = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI, 9025c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_PROJECTION, 9035c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs, 9045c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.MAILBOX_KEY); 9055c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 9065c523858385176c33a7456bb84035de78552d22dMarc Blank try { 9075c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 9085c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 9095c523858385176c33a7456bb84035de78552d22dMarc Blank // Demand load mailbox (note order-by to reduce thrashing here) 9105c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = null; 9115c523858385176c33a7456bb84035de78552d22dMarc Blank // loop through messages marked as needing updates 9125c523858385176c33a7456bb84035de78552d22dMarc Blank while (updates.moveToNext()) { 9135c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeMoveToTrash = false; 9145c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeRead = false; 9155c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeFlagged = false; 9165c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeMailbox = false; 9175c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeAnswered = false; 9185c523858385176c33a7456bb84035de78552d22dMarc Blank 9195c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message oldMessage = 920c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.getContent(updates, EmailContent.Message.class); 9215c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = oldMessage.mId; 9225c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message newMessage = 923c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.restoreMessageWithId(context, oldMessage.mId); 9245c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage != null) { 9255c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = Mailbox.restoreMailboxWithId(context, newMessage.mMailboxKey); 9265c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 9275c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 9285c523858385176c33a7456bb84035de78552d22dMarc Blank } 9295c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMessage.mMailboxKey != newMessage.mMailboxKey) { 9305c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_TRASH) { 9315c523858385176c33a7456bb84035de78552d22dMarc Blank changeMoveToTrash = true; 9325c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 9335c523858385176c33a7456bb84035de78552d22dMarc Blank changeMailbox = true; 9345c523858385176c33a7456bb84035de78552d22dMarc Blank } 9355c523858385176c33a7456bb84035de78552d22dMarc Blank } 9365c523858385176c33a7456bb84035de78552d22dMarc Blank changeRead = oldMessage.mFlagRead != newMessage.mFlagRead; 9375c523858385176c33a7456bb84035de78552d22dMarc Blank changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite; 9385c523858385176c33a7456bb84035de78552d22dMarc Blank changeAnswered = (oldMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 939c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO); 940c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 9415c523858385176c33a7456bb84035de78552d22dMarc Blank 9425c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 9435c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null && 9445c523858385176c33a7456bb84035de78552d22dMarc Blank (changeMoveToTrash || changeRead || changeFlagged || changeMailbox || 9455c523858385176c33a7456bb84035de78552d22dMarc Blank changeAnswered)) { 9465c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 9475c523858385176c33a7456bb84035de78552d22dMarc Blank } 9485c523858385176c33a7456bb84035de78552d22dMarc Blank 9495c523858385176c33a7456bb84035de78552d22dMarc Blank // Dispatch here for specific change types 9505c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeMoveToTrash) { 9515c523858385176c33a7456bb84035de78552d22dMarc Blank // Move message to trash 9527d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processPendingMoveToTrash(context, remoteStore, mailbox, oldMessage, 9535c523858385176c33a7456bb84035de78552d22dMarc Blank newMessage); 9545c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (changeRead || changeFlagged || changeMailbox || changeAnswered) { 9555c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingDataChange(context, remoteStore, mailbox, changeRead, 9565c523858385176c33a7456bb84035de78552d22dMarc Blank changeFlagged, changeMailbox, changeAnswered, oldMessage, newMessage); 9575c523858385176c33a7456bb84035de78552d22dMarc Blank } 9585c523858385176c33a7456bb84035de78552d22dMarc Blank 9595c523858385176c33a7456bb84035de78552d22dMarc Blank // Finally, delete the update 9605c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI, 9615c523858385176c33a7456bb84035de78552d22dMarc Blank oldMessage.mId); 9625c523858385176c33a7456bb84035de78552d22dMarc Blank resolver.delete(uri, null, null); 9635c523858385176c33a7456bb84035de78552d22dMarc Blank } 9645c523858385176c33a7456bb84035de78552d22dMarc Blank 9655c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 9665c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 9675c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 9685c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 969560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending update for id=" 970c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + lastMessageId + ": " + me); 9715c523858385176c33a7456bb84035de78552d22dMarc Blank } 9725c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 9735c523858385176c33a7456bb84035de78552d22dMarc Blank updates.close(); 9745c523858385176c33a7456bb84035de78552d22dMarc Blank } 9755c523858385176c33a7456bb84035de78552d22dMarc Blank } 9765c523858385176c33a7456bb84035de78552d22dMarc Blank 9775c523858385176c33a7456bb84035de78552d22dMarc Blank /** 978c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Upsync an entire message. This must also unwind whatever triggered it (either by 9795c523858385176c33a7456bb84035de78552d22dMarc Blank * updating the serverId, or by deleting the update record, or it's going to keep happening 9805c523858385176c33a7456bb84035de78552d22dMarc Blank * over and over again. 9815c523858385176c33a7456bb84035de78552d22dMarc Blank * 982c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload. 983c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * This is to avoid unnecessary uploads into the trash. Although the caller attempts to select 9845c523858385176c33a7456bb84035de78552d22dMarc Blank * only the Drafts and Sent folders, this can happen when the update record and the current 985c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * record mismatch. In this case, we let the update record remain, because the filters 9865c523858385176c33a7456bb84035de78552d22dMarc Blank * in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it) 9875c523858385176c33a7456bb84035de78552d22dMarc Blank * appropriately. 9885c523858385176c33a7456bb84035de78552d22dMarc Blank * 9895c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the actual mailbox 9905c523858385176c33a7456bb84035de78552d22dMarc Blank */ 9917d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private static void processUploadMessage(Context context, Store remoteStore, Mailbox mailbox, 9927d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler long messageId) 9935c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 9945c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message newMessage = 995c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.restoreMessageWithId(context, messageId); 996c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu final boolean deleteUpdate; 9975c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage == null) { 9985c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = true; 999560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync failed for null message, id=" + messageId); 10005c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_DRAFTS) { 10015c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1002560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId); 10035c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 10045c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1005560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId); 10065c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_TRASH) { 10075c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1008560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId); 10091b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy } else if (newMessage.mMailboxKey != mailbox.mId) { 10105c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1011560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped; mailbox changed, id=" + messageId); 10125c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 1013560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsyc triggered for message id=" + messageId); 10147d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler deleteUpdate = processPendingAppend(context, remoteStore, mailbox, newMessage); 10155c523858385176c33a7456bb84035de78552d22dMarc Blank } 10165c523858385176c33a7456bb84035de78552d22dMarc Blank if (deleteUpdate) { 10175c523858385176c33a7456bb84035de78552d22dMarc Blank // Finally, delete the update (if any) 10185c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId( 10195c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.UPDATED_CONTENT_URI, messageId); 10205c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().delete(uri, null, null); 10215c523858385176c33a7456bb84035de78552d22dMarc Blank } 10225c523858385176c33a7456bb84035de78552d22dMarc Blank } 10235c523858385176c33a7456bb84035de78552d22dMarc Blank 10245c523858385176c33a7456bb84035de78552d22dMarc Blank /** 10255c523858385176c33a7456bb84035de78552d22dMarc Blank * Upsync changes to read, flagged, or mailbox 10265c523858385176c33a7456bb84035de78552d22dMarc Blank * 10275c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store for this mailbox 10285c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the mailbox the message is stored in 10295c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeRead whether the message's read state has changed 10305c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeFlagged whether the message's flagged state has changed 10315c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeMailbox whether the message's mailbox has changed 10325c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage the message in it's pre-change state 10335c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMessage the current version of the message 10345c523858385176c33a7456bb84035de78552d22dMarc Blank */ 10355c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingDataChange(final Context context, Store remoteStore, 10365c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox, boolean changeRead, boolean changeFlagged, boolean changeMailbox, 10375c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeAnswered, EmailContent.Message oldMessage, 10385c523858385176c33a7456bb84035de78552d22dMarc Blank final EmailContent.Message newMessage) throws MessagingException { 10395c523858385176c33a7456bb84035de78552d22dMarc Blank // New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't 10405c523858385176c33a7456bb84035de78552d22dMarc Blank // being moved 10415c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox newMailbox = mailbox; 10425c523858385176c33a7456bb84035de78552d22dMarc Blank // Mailbox is the original remote mailbox (the one we're acting on) 10435c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = getRemoteMailboxForMessage(context, oldMessage); 10445c523858385176c33a7456bb84035de78552d22dMarc Blank 10455c523858385176c33a7456bb84035de78552d22dMarc Blank // 0. No remote update if the message is local-only 10465c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage.mServerId == null || newMessage.mServerId.equals("") 10475c523858385176c33a7456bb84035de78552d22dMarc Blank || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX) || (mailbox == null)) { 10485c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10495c523858385176c33a7456bb84035de78552d22dMarc Blank } 10505c523858385176c33a7456bb84035de78552d22dMarc Blank 10515c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. No remote update for DRAFTS or OUTBOX 10525c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) { 10535c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10545c523858385176c33a7456bb84035de78552d22dMarc Blank } 10555c523858385176c33a7456bb84035de78552d22dMarc Blank 10565c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. Open the remote store & folder 10575c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 10585c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 10595c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10605c523858385176c33a7456bb84035de78552d22dMarc Blank } 10615c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 10625c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 10635c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10645c523858385176c33a7456bb84035de78552d22dMarc Blank } 10655c523858385176c33a7456bb84035de78552d22dMarc Blank 10665c523858385176c33a7456bb84035de78552d22dMarc Blank // 3. Finally, apply the changes to the message 10675c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId); 10685c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 10695c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10705c523858385176c33a7456bb84035de78552d22dMarc Blank } 10715c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 1072560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, 10735c523858385176c33a7456bb84035de78552d22dMarc Blank "Update for msg id=" + newMessage.mId 10745c523858385176c33a7456bb84035de78552d22dMarc Blank + " read=" + newMessage.mFlagRead 10755c523858385176c33a7456bb84035de78552d22dMarc Blank + " flagged=" + newMessage.mFlagFavorite 10765c523858385176c33a7456bb84035de78552d22dMarc Blank + " answered=" 10775c523858385176c33a7456bb84035de78552d22dMarc Blank + ((newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0) 10785c523858385176c33a7456bb84035de78552d22dMarc Blank + " new mailbox=" + newMessage.mMailboxKey); 10795c523858385176c33a7456bb84035de78552d22dMarc Blank } 10805c523858385176c33a7456bb84035de78552d22dMarc Blank Message[] messages = new Message[] { remoteMessage }; 10815c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeRead) { 10825c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_SEEN, newMessage.mFlagRead); 10835c523858385176c33a7456bb84035de78552d22dMarc Blank } 10845c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeFlagged) { 10855c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite); 10865c523858385176c33a7456bb84035de78552d22dMarc Blank } 10875c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeAnswered) { 10885c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_ANSWERED, 10895c523858385176c33a7456bb84035de78552d22dMarc Blank (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0); 10905c523858385176c33a7456bb84035de78552d22dMarc Blank } 10915c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeMailbox) { 10925c523858385176c33a7456bb84035de78552d22dMarc Blank Folder toFolder = remoteStore.getFolder(newMailbox.mServerId); 10935c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 10945c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10955c523858385176c33a7456bb84035de78552d22dMarc Blank } 10965c523858385176c33a7456bb84035de78552d22dMarc Blank // We may need the message id to search for the message in the destination folder 10975c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setMessageId(newMessage.mMessageId); 10985c523858385176c33a7456bb84035de78552d22dMarc Blank // Copy the message to its new folder 10995c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.copyMessages(messages, toFolder, new MessageUpdateCallbacks() { 11005c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 11015c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageUidChange(Message message, String newUid) { 11025c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues cv = new ContentValues(); 11035c523858385176c33a7456bb84035de78552d22dMarc Blank cv.put(EmailContent.Message.SERVER_ID, newUid); 11045c523858385176c33a7456bb84035de78552d22dMarc Blank // We only have one message, so, any updates _must_ be for it. Otherwise, 11055c523858385176c33a7456bb84035de78552d22dMarc Blank // we'd have to cycle through to find the one with the same server ID. 11065c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().update(ContentUris.withAppendedId( 11075c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null); 11085c523858385176c33a7456bb84035de78552d22dMarc Blank } 1109c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 11105c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 11115c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageNotFound(Message message) { 11125c523858385176c33a7456bb84035de78552d22dMarc Blank } 11135c523858385176c33a7456bb84035de78552d22dMarc Blank }); 11145c523858385176c33a7456bb84035de78552d22dMarc Blank // Delete the message from the remote source folder 11155c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 11165c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.expunge(); 11175c523858385176c33a7456bb84035de78552d22dMarc Blank } 11185c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11195c523858385176c33a7456bb84035de78552d22dMarc Blank } 11205c523858385176c33a7456bb84035de78552d22dMarc Blank 11215c523858385176c33a7456bb84035de78552d22dMarc Blank /** 11225c523858385176c33a7456bb84035de78552d22dMarc Blank * Process a pending trash message command. 11235c523858385176c33a7456bb84035de78552d22dMarc Blank * 11245c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store we're working in 11255c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMailbox The local trash mailbox 11265c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage The message copy that was saved in the updates shadow table 11275c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMessage The message that was moved to the mailbox 11285c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11295c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingMoveToTrash(final Context context, Store remoteStore, 11307d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Mailbox newMailbox, EmailContent.Message oldMessage, 11315c523858385176c33a7456bb84035de78552d22dMarc Blank final EmailContent.Message newMessage) throws MessagingException { 11325c523858385176c33a7456bb84035de78552d22dMarc Blank 11335c523858385176c33a7456bb84035de78552d22dMarc Blank // 0. No remote move if the message is local-only 11345c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage.mServerId == null || newMessage.mServerId.equals("") 11355c523858385176c33a7456bb84035de78552d22dMarc Blank || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) { 11365c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11375c523858385176c33a7456bb84035de78552d22dMarc Blank } 11385c523858385176c33a7456bb84035de78552d22dMarc Blank 11395c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. Escape early if we can't find the local mailbox 11405c523858385176c33a7456bb84035de78552d22dMarc Blank // TODO smaller projection here 11415c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox oldMailbox = getRemoteMailboxForMessage(context, oldMessage); 11425c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox == null) { 11435c523858385176c33a7456bb84035de78552d22dMarc Blank // can't find old mailbox, it may have been deleted. just return. 11445c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11455c523858385176c33a7456bb84035de78552d22dMarc Blank } 11465c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. We don't support delete-from-trash here 11475c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox.mType == Mailbox.TYPE_TRASH) { 11485c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11495c523858385176c33a7456bb84035de78552d22dMarc Blank } 11505c523858385176c33a7456bb84035de78552d22dMarc Blank 11515c523858385176c33a7456bb84035de78552d22dMarc Blank // The rest of this method handles server-side deletion 11525c523858385176c33a7456bb84035de78552d22dMarc Blank 11535c523858385176c33a7456bb84035de78552d22dMarc Blank // 4. Find the remote mailbox (that we deleted from), and open it 11545c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteFolder = remoteStore.getFolder(oldMailbox.mServerId); 11555c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 11565c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11575c523858385176c33a7456bb84035de78552d22dMarc Blank } 11585c523858385176c33a7456bb84035de78552d22dMarc Blank 11595c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 11605c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 11615c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11625c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11635c523858385176c33a7456bb84035de78552d22dMarc Blank } 11645c523858385176c33a7456bb84035de78552d22dMarc Blank 11655c523858385176c33a7456bb84035de78552d22dMarc Blank // 5. Find the remote original message 11665c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteFolder.getMessage(oldMessage.mServerId); 11675c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 11685c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11695c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11705c523858385176c33a7456bb84035de78552d22dMarc Blank } 11715c523858385176c33a7456bb84035de78552d22dMarc Blank 11725c523858385176c33a7456bb84035de78552d22dMarc Blank // 6. Find the remote trash folder, and create it if not found 11735c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteTrashFolder = remoteStore.getFolder(newMailbox.mServerId); 11745c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteTrashFolder.exists()) { 11755c523858385176c33a7456bb84035de78552d22dMarc Blank /* 11765c523858385176c33a7456bb84035de78552d22dMarc Blank * If the remote trash folder doesn't exist we try to create it. 11775c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11785c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.create(FolderType.HOLDS_MESSAGES); 11795c523858385176c33a7456bb84035de78552d22dMarc Blank } 11805c523858385176c33a7456bb84035de78552d22dMarc Blank 1181c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 7. Try to copy the message into the remote trash folder 11825c523858385176c33a7456bb84035de78552d22dMarc Blank // Note, this entire section will be skipped for POP3 because there's no remote trash 11835c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.exists()) { 11845c523858385176c33a7456bb84035de78552d22dMarc Blank /* 11855c523858385176c33a7456bb84035de78552d22dMarc Blank * Because remoteTrashFolder may be new, we need to explicitly open it 11865c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11875c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.open(OpenMode.READ_WRITE); 11885c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) { 11895c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11905c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 11915c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11925c523858385176c33a7456bb84035de78552d22dMarc Blank } 11935c523858385176c33a7456bb84035de78552d22dMarc Blank 11945c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder, 11955c523858385176c33a7456bb84035de78552d22dMarc Blank new Folder.MessageUpdateCallbacks() { 11965c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 11975c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageUidChange(Message message, String newUid) { 11985c523858385176c33a7456bb84035de78552d22dMarc Blank // update the UID in the local trash folder, because some stores will 11995c523858385176c33a7456bb84035de78552d22dMarc Blank // have to change it when copying to remoteTrashFolder 12005c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues cv = new ContentValues(); 12015c523858385176c33a7456bb84035de78552d22dMarc Blank cv.put(EmailContent.Message.SERVER_ID, newUid); 12025c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().update(newMessage.getUri(), cv, null, null); 12035c523858385176c33a7456bb84035de78552d22dMarc Blank } 12045c523858385176c33a7456bb84035de78552d22dMarc Blank 12055c523858385176c33a7456bb84035de78552d22dMarc Blank /** 12065c523858385176c33a7456bb84035de78552d22dMarc Blank * This will be called if the deleted message doesn't exist and can't be 12075c523858385176c33a7456bb84035de78552d22dMarc Blank * deleted (e.g. it was already deleted from the server.) In this case, 12085c523858385176c33a7456bb84035de78552d22dMarc Blank * attempt to delete the local copy as well. 12095c523858385176c33a7456bb84035de78552d22dMarc Blank */ 12105c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 12115c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageNotFound(Message message) { 12125c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().delete(newMessage.getUri(), null, null); 12135c523858385176c33a7456bb84035de78552d22dMarc Blank } 12145c523858385176c33a7456bb84035de78552d22dMarc Blank }); 12155c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12165c523858385176c33a7456bb84035de78552d22dMarc Blank } 12175c523858385176c33a7456bb84035de78552d22dMarc Blank 12185c523858385176c33a7456bb84035de78552d22dMarc Blank // 8. Delete the message from the remote source folder 12195c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 12205c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.expunge(); 12215c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 12225c523858385176c33a7456bb84035de78552d22dMarc Blank } 12235c523858385176c33a7456bb84035de78552d22dMarc Blank 12245c523858385176c33a7456bb84035de78552d22dMarc Blank /** 12255c523858385176c33a7456bb84035de78552d22dMarc Blank * Process a pending trash message command. 12265c523858385176c33a7456bb84035de78552d22dMarc Blank * 12275c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store we're working in 12285c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMailbox The local trash mailbox 12295c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage The message that was deleted from the trash 12305c523858385176c33a7456bb84035de78552d22dMarc Blank */ 12311b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static void processPendingDeleteFromTrash(Store remoteStore, 12327d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Mailbox oldMailbox, EmailContent.Message oldMessage) 12335c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 12345c523858385176c33a7456bb84035de78552d22dMarc Blank 12355c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. We only support delete-from-trash here 12365c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox.mType != Mailbox.TYPE_TRASH) { 12375c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12385c523858385176c33a7456bb84035de78552d22dMarc Blank } 12395c523858385176c33a7456bb84035de78552d22dMarc Blank 12405c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. Find the remote trash folder (that we are deleting from), and open it 12415c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteTrashFolder = remoteStore.getFolder(oldMailbox.mServerId); 12425c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteTrashFolder.exists()) { 12435c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12445c523858385176c33a7456bb84035de78552d22dMarc Blank } 12455c523858385176c33a7456bb84035de78552d22dMarc Blank 12465c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.open(OpenMode.READ_WRITE); 12475c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) { 12485c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12495c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12505c523858385176c33a7456bb84035de78552d22dMarc Blank } 12515c523858385176c33a7456bb84035de78552d22dMarc Blank 12525c523858385176c33a7456bb84035de78552d22dMarc Blank // 3. Find the remote original message 12535c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteTrashFolder.getMessage(oldMessage.mServerId); 12545c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 12555c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12565c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12575c523858385176c33a7456bb84035de78552d22dMarc Blank } 12585c523858385176c33a7456bb84035de78552d22dMarc Blank 12595c523858385176c33a7456bb84035de78552d22dMarc Blank // 4. Delete the message from the remote trash folder 12605c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 12615c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.expunge(); 12625c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12635c523858385176c33a7456bb84035de78552d22dMarc Blank } 12645c523858385176c33a7456bb84035de78552d22dMarc Blank 12655c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1266c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * Process a pending append message command. This command uploads a local message to the 1267c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * server, first checking to be sure that the server message is not newer than 1268c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * the local message. 1269c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * 1270c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param remoteStore the remote store we're working in 1271c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param mailbox The mailbox we're appending to 1272c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param message The message we're appending 1273c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @return true if successfully uploaded 1274c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu */ 12757d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private static boolean processPendingAppend(Context context, Store remoteStore, Mailbox mailbox, 12767d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler EmailContent.Message message) 1277c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu throws MessagingException { 1278c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean updateInternalDate = false; 1279c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean updateMessage = false; 1280c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean deleteMessage = false; 1281c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1282c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 1. Find the remote folder that we're appending to and create and/or open it 1283c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 1284c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (!remoteFolder.exists()) { 1285c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { 1286c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // This is a (hopefully) transient error and we return false to try again later 1287c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return false; 1288c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1289c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1290c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.open(OpenMode.READ_WRITE); 1291c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 1292c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return false; 1293c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1294c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1295c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 2. If possible, load a remote message with the matching UID 1296c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message remoteMessage = null; 1297c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (message.mServerId != null && message.mServerId.length() > 0) { 1298c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteMessage = remoteFolder.getMessage(message.mServerId); 1299c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1300c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1301c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3. If a remote message could not be found, upload our local message 1302c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteMessage == null) { 130337b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: 130437b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // if we have a serverId and remoteMessage is still null, then probably the message 130537b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // has been deleted and we should delete locally. 1306c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3a. Create a legacy message to upload 1307c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message localMessage = LegacyConversions.makeMessage(context, message); 1308c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3b. Upload it 13097d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler //FetchProfile fp = new FetchProfile(); 13107d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler //fp.add(FetchProfile.Item.BODY); 131137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // Note that this operation will assign the Uid to localMessage 1312c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.appendMessages(new Message[] { localMessage }); 1313c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1314c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3b. And record the UID from the server 1315c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerId = localMessage.getUid(); 1316c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateInternalDate = true; 1317c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1318c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else { 1319c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4. If the remote message exists we need to determine which copy to keep. 132037b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: 132137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // I don't see a good reason we should be here. If the message already has a serverId, 132237b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // then we should be handling it in processPendingUpdates(), 132337b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // not processPendingUploads() 1324c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu FetchProfile fp = new FetchProfile(); 1325c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.add(FetchProfile.Item.ENVELOPE); 1326c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); 1327c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Date localDate = new Date(message.mServerTimeStamp); 1328c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Date remoteDate = remoteMessage.getInternalDate(); 1329c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteDate != null && remoteDate.compareTo(localDate) > 0) { 1330c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4a. If the remote message is newer than ours we'll just 1331c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // delete ours and move on. A sync will get the server message 1332c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // if we need to be able to see it. 1333c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu deleteMessage = true; 1334c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else { 1335c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4b. Otherwise we'll upload our message and then delete the remote message. 1336c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1337c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // Create a legacy message to upload 133837b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: This strategy has a problem: This will create a second message, 133937b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // so that at least temporarily, we will have two messages for what the 134037b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // user would think of as one. 1341c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message localMessage = LegacyConversions.makeMessage(context, message); 1342c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1343c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4c. Upload it 1344c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.clear(); 1345c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp = new FetchProfile(); 1346c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.add(FetchProfile.Item.BODY); 1347c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.appendMessages(new Message[] { localMessage }); 1348c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1349c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4d. Record the UID and new internalDate from the server 1350c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerId = localMessage.getUid(); 1351c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateInternalDate = true; 1352c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1353c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 135437b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // 4e. And delete the old copy of the message from the server. 1355c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteMessage.setFlag(Flag.DELETED, true); 1356c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1357c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1358c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1359c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 5. If requested, Best-effort to capture new "internaldate" from the server 1360c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (updateInternalDate && message.mServerId != null) { 1361c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu try { 1362c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message remoteMessage2 = remoteFolder.getMessage(message.mServerId); 1363c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteMessage2 != null) { 1364c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu FetchProfile fp2 = new FetchProfile(); 1365c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp2.add(FetchProfile.Item.ENVELOPE); 1366c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.fetch(new Message[] { remoteMessage2 }, fp2, null); 1367c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerTimeStamp = remoteMessage2.getInternalDate().getTime(); 1368c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1369c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1370c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } catch (MessagingException me) { 1371c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // skip it - we can live without this 1372c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1373c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1374c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1375c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 6. Perform required edits to local copy of message 1376c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (deleteMessage || updateMessage) { 1377c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId); 1378c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu ContentResolver resolver = context.getContentResolver(); 1379c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (deleteMessage) { 1380c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu resolver.delete(uri, null, null); 1381c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else if (updateMessage) { 1382c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu ContentValues cv = new ContentValues(); 1383c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu cv.put(EmailContent.Message.SERVER_ID, message.mServerId); 1384c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu cv.put(EmailContent.Message.SERVER_TIMESTAMP, message.mServerTimeStamp); 1385c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu resolver.update(uri, cv, null, null); 1386c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1387c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1388c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1389c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return true; 1390c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1391c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1392c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu /** 13935c523858385176c33a7456bb84035de78552d22dMarc Blank * A message and numeric uid that's easily sortable 13945c523858385176c33a7456bb84035de78552d22dMarc Blank */ 13955c523858385176c33a7456bb84035de78552d22dMarc Blank private static class SortableMessage { 13965c523858385176c33a7456bb84035de78552d22dMarc Blank private final Message mMessage; 13975c523858385176c33a7456bb84035de78552d22dMarc Blank private final long mUid; 13985c523858385176c33a7456bb84035de78552d22dMarc Blank 13995c523858385176c33a7456bb84035de78552d22dMarc Blank SortableMessage(Message message, long uid) { 14005c523858385176c33a7456bb84035de78552d22dMarc Blank mMessage = message; 14015c523858385176c33a7456bb84035de78552d22dMarc Blank mUid = uid; 14025c523858385176c33a7456bb84035de78552d22dMarc Blank } 14035c523858385176c33a7456bb84035de78552d22dMarc Blank } 14045c523858385176c33a7456bb84035de78552d22dMarc Blank 14051b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static int searchMailboxImpl(final Context context, final long accountId, 14061b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy final SearchParams searchParams, final long destMailboxId) throws MessagingException { 14075c523858385176c33a7456bb84035de78552d22dMarc Blank final Account account = Account.restoreAccountWithId(context, accountId); 14085c523858385176c33a7456bb84035de78552d22dMarc Blank final Mailbox mailbox = Mailbox.restoreMailboxWithId(context, searchParams.mMailboxId); 14095c523858385176c33a7456bb84035de78552d22dMarc Blank final Mailbox destMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId); 14105c523858385176c33a7456bb84035de78552d22dMarc Blank if (account == null || mailbox == null || destMailbox == null) { 1411560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Attempted search for " + searchParams 14125c523858385176c33a7456bb84035de78552d22dMarc Blank + " but account or mailbox information was missing"); 14135c523858385176c33a7456bb84035de78552d22dMarc Blank return 0; 14145c523858385176c33a7456bb84035de78552d22dMarc Blank } 14155c523858385176c33a7456bb84035de78552d22dMarc Blank 14165c523858385176c33a7456bb84035de78552d22dMarc Blank // Tell UI that we're loading messages 1417af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler final ContentValues statusValues = new ContentValues(2); 1418af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY); 1419af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler destMailbox.update(context, statusValues); 14205c523858385176c33a7456bb84035de78552d22dMarc Blank 1421768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Store remoteStore = Store.getInstance(account, context); 1422768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 14235c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 14245c523858385176c33a7456bb84035de78552d22dMarc Blank 14255c523858385176c33a7456bb84035de78552d22dMarc Blank SortableMessage[] sortableMessages = new SortableMessage[0]; 14265c523858385176c33a7456bb84035de78552d22dMarc Blank if (searchParams.mOffset == 0) { 14275c523858385176c33a7456bb84035de78552d22dMarc Blank // Get the "bare" messages (basically uid) 1428768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Message[] remoteMessages = remoteFolder.getMessages(searchParams, null); 1429768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final int remoteCount = remoteMessages.length; 14305c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteCount > 0) { 14315c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages = new SortableMessage[remoteCount]; 14325c523858385176c33a7456bb84035de78552d22dMarc Blank int i = 0; 14335c523858385176c33a7456bb84035de78552d22dMarc Blank for (Message msg : remoteMessages) { 14345c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages[i++] = new SortableMessage(msg, Long.parseLong(msg.getUid())); 14355c523858385176c33a7456bb84035de78552d22dMarc Blank } 14365c523858385176c33a7456bb84035de78552d22dMarc Blank // Sort the uid's, most recent first 14375c523858385176c33a7456bb84035de78552d22dMarc Blank // Note: Not all servers will be nice and return results in the order of request; 14385c523858385176c33a7456bb84035de78552d22dMarc Blank // those that do will see messages arrive from newest to oldest 14395c523858385176c33a7456bb84035de78552d22dMarc Blank Arrays.sort(sortableMessages, new Comparator<SortableMessage>() { 14405c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 14415c523858385176c33a7456bb84035de78552d22dMarc Blank public int compare(SortableMessage lhs, SortableMessage rhs) { 14425c523858385176c33a7456bb84035de78552d22dMarc Blank return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0; 14435c523858385176c33a7456bb84035de78552d22dMarc Blank } 14445c523858385176c33a7456bb84035de78552d22dMarc Blank }); 14455c523858385176c33a7456bb84035de78552d22dMarc Blank sSearchResults.put(accountId, sortableMessages); 14465c523858385176c33a7456bb84035de78552d22dMarc Blank } 14475c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 14487dad461e9e0c17bc909da2afbd8023cf7059d931Martin Hibdon // It seems odd for this to happen, but if the previous query returned zero results, 14497dad461e9e0c17bc909da2afbd8023cf7059d931Martin Hibdon // but the UI somehow still attempted to load more, then sSearchResults will have 14507dad461e9e0c17bc909da2afbd8023cf7059d931Martin Hibdon // a null value for this account. We need to handle this below. 14515c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages = sSearchResults.get(accountId); 14525c523858385176c33a7456bb84035de78552d22dMarc Blank } 14535c523858385176c33a7456bb84035de78552d22dMarc Blank 14547dad461e9e0c17bc909da2afbd8023cf7059d931Martin Hibdon final int numSearchResults = (sortableMessages != null ? sortableMessages.length : 0); 14555c523858385176c33a7456bb84035de78552d22dMarc Blank final int numToLoad = 1456c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit); 14577d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler destMailbox.updateMessageCount(context, numSearchResults); 14585c523858385176c33a7456bb84035de78552d22dMarc Blank if (numToLoad <= 0) { 14595c523858385176c33a7456bb84035de78552d22dMarc Blank return 0; 14605c523858385176c33a7456bb84035de78552d22dMarc Blank } 14615c523858385176c33a7456bb84035de78552d22dMarc Blank 14625c523858385176c33a7456bb84035de78552d22dMarc Blank final ArrayList<Message> messageList = new ArrayList<Message>(); 14635c523858385176c33a7456bb84035de78552d22dMarc Blank for (int i = searchParams.mOffset; i < numToLoad + searchParams.mOffset; i++) { 14645c523858385176c33a7456bb84035de78552d22dMarc Blank messageList.add(sortableMessages[i].mMessage); 14655c523858385176c33a7456bb84035de78552d22dMarc Blank } 1466d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // First fetch FLAGS and ENVELOPE. In a second pass, we'll fetch STRUCTURE and 1467d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // the first body part. 1468d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon final FetchProfile fp = new FetchProfile(); 14695c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.FLAGS); 14705c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.ENVELOPE); 1471d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 1472d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon Message[] messageArray = messageList.toArray(new Message[messageList.size()]); 1473d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 1474d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // TODO: Why should we do this with a messageRetrievalListener? It updates the messages 1475d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // directly in the messageArray. After making this call, we could simply walk it 1476d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // and do all of these operations ourselves. 1477d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon remoteFolder.fetch(messageArray, fp, new MessageRetrievalListener() { 14785c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 14795c523858385176c33a7456bb84035de78552d22dMarc Blank public void messageRetrieved(Message message) { 1480d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // TODO: Why do we have two separate try/catch blocks here? 1481d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // After MR1, we should consolidate this. 14825c523858385176c33a7456bb84035de78552d22dMarc Blank try { 14835c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message localMessage = new EmailContent.Message(); 1484d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 14855c523858385176c33a7456bb84035de78552d22dMarc Blank try { 14865c523858385176c33a7456bb84035de78552d22dMarc Blank // Copy the fields that are available into the message 14875c523858385176c33a7456bb84035de78552d22dMarc Blank LegacyConversions.updateMessageFields(localMessage, 14885c523858385176c33a7456bb84035de78552d22dMarc Blank message, account.mId, mailbox.mId); 1489c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // Save off the mailbox that this message *really* belongs in. 1490c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // We need this information if we need to do more lookups 1491c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // (like loading attachments) for this message. See b/11294681 1492c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon localMessage.mMainMailboxKey = localMessage.mMailboxKey; 14935c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage.mMailboxKey = destMailboxId; 14945c523858385176c33a7456bb84035de78552d22dMarc Blank // We load 50k or so; maybe it's complete, maybe not... 14955c523858385176c33a7456bb84035de78552d22dMarc Blank int flag = EmailContent.Message.FLAG_LOADED_COMPLETE; 14965c523858385176c33a7456bb84035de78552d22dMarc Blank // We store the serverId of the source mailbox into protocolSearchInfo 14975c523858385176c33a7456bb84035de78552d22dMarc Blank // This will be used by loadMessageForView, etc. to use the proper remote 14985c523858385176c33a7456bb84035de78552d22dMarc Blank // folder 14995c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage.mProtocolSearchInfo = mailbox.mServerId; 1500d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Commit the message to the local store 1501d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon Utilities.saveOrUpdate(localMessage, context); 15025c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 1503560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 15045c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while copying downloaded message." + me); 15055c523858385176c33a7456bb84035de78552d22dMarc Blank } 15065c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (Exception e) { 1507560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 15085c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while storing downloaded message." + e.toString()); 15095c523858385176c33a7456bb84035de78552d22dMarc Blank } 15105c523858385176c33a7456bb84035de78552d22dMarc Blank } 15115c523858385176c33a7456bb84035de78552d22dMarc Blank 15125c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 15135c523858385176c33a7456bb84035de78552d22dMarc Blank public void loadAttachmentProgress(int progress) { 15145c523858385176c33a7456bb84035de78552d22dMarc Blank } 15155c523858385176c33a7456bb84035de78552d22dMarc Blank }); 1516d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 1517d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Now load the structure for all of the messages: 1518d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon fp.clear(); 1519d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon fp.add(FetchProfile.Item.STRUCTURE); 1520d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon remoteFolder.fetch(messageArray, fp, null); 1521d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 1522d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Finally, load the first body part (i.e. message text). 1523d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // This means attachment contents are not yet loaded, but that's okay, 1524d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // we'll load them as needed, same as in synced messages. 1525d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon Message [] oneMessageArray = new Message[1]; 1526d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon for (Message message : messageArray) { 1527d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Build a list of parts we are interested in. Text parts will be downloaded 1528d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // right now, attachments will be left for later. 1529d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon ArrayList<Part> viewables = new ArrayList<Part>(); 1530d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon ArrayList<Part> attachments = new ArrayList<Part>(); 1531d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon MimeUtility.collectParts(message, viewables, attachments); 1532d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Download the viewables immediately 1533d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon oneMessageArray[0] = message; 1534d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon for (Part part : viewables) { 1535d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon fp.clear(); 1536d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon fp.add(part); 1537d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon remoteFolder.fetch(oneMessageArray, fp, null); 1538d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon } 1539d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon // Store the updated message locally and mark it fully loaded 1540d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon Utilities.copyOneMessageToProvider(context, message, account, destMailbox, 1541d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon EmailContent.Message.FLAG_LOADED_COMPLETE); 1542d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon } 1543d482cbd54b3e4f9ed889bc622069fa4fba4e4416Martin Hibdon 1544af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler // Tell UI that we're done loading messages 1545af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 1546af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); 1547af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler destMailbox.update(context, statusValues); 1548af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler 15495c523858385176c33a7456bb84035de78552d22dMarc Blank return numSearchResults; 15505c523858385176c33a7456bb84035de78552d22dMarc Blank } 15512075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu} 1552