ImapService.java revision c86fbb5bcbff72102f87747d94948f0749402229
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 } 3165c523858385176c33a7456bb84035de78552d22dMarc Blank catch (Exception e) { 317560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 3185c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while storing downloaded message." + e.toString()); 3195c523858385176c33a7456bb84035de78552d22dMarc Blank } 3205c523858385176c33a7456bb84035de78552d22dMarc Blank } 3215c523858385176c33a7456bb84035de78552d22dMarc Blank 3225c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 3235c523858385176c33a7456bb84035de78552d22dMarc Blank public void loadAttachmentProgress(int progress) { 3245c523858385176c33a7456bb84035de78552d22dMarc Blank } 3255c523858385176c33a7456bb84035de78552d22dMarc Blank }); 3265c523858385176c33a7456bb84035de78552d22dMarc Blank 3275c523858385176c33a7456bb84035de78552d22dMarc Blank } 3285c523858385176c33a7456bb84035de78552d22dMarc Blank 3295c523858385176c33a7456bb84035de78552d22dMarc Blank /** 3305c523858385176c33a7456bb84035de78552d22dMarc Blank * Synchronizer for IMAP. 3315c523858385176c33a7456bb84035de78552d22dMarc Blank * 3325c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO Break this method up into smaller chunks. 3335c523858385176c33a7456bb84035de78552d22dMarc Blank * 3345c523858385176c33a7456bb84035de78552d22dMarc Blank * @param account the account to sync 3355c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the mailbox to sync 336c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * @param loadMore whether we should be loading more older messages 337c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * @param uiRefresh whether this request is in response to a user action 3385c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 3395c523858385176c33a7456bb84035de78552d22dMarc Blank */ 3407d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private synchronized static void synchronizeMailboxGeneric(final Context context, 3417d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final Account account, final Mailbox mailbox, final boolean loadMore, 3427d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final boolean uiRefresh) 343c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon throws MessagingException { 344c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 345466eb2dcd2660bf0e5d8d1440db598a30ca184f3Martin Hibdon LogUtils.d(Logging.LOG_TAG, "synchronizeMailboxGeneric " + account + " " + mailbox + " " 346c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + loadMore + " " + uiRefresh); 34725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 3485c523858385176c33a7456bb84035de78552d22dMarc Blank final ArrayList<Long> unseenMessages = new ArrayList<Long>(); 3495c523858385176c33a7456bb84035de78552d22dMarc Blank 3505c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 3515c523858385176c33a7456bb84035de78552d22dMarc Blank 352c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 0. We do not ever sync DRAFTS or OUTBOX (down or up) 3535c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) { 3545c523858385176c33a7456bb84035de78552d22dMarc Blank return; 3555c523858385176c33a7456bb84035de78552d22dMarc Blank } 3565c523858385176c33a7456bb84035de78552d22dMarc Blank 357c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 1. Figure out what our sync window should be. 358c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long endDate; 359c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 360c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // We will do a full sync if the user has actively requested a sync, or if it has been 361c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // too long since the last full sync. 362c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If we have rebooted since the last full sync, then we may get a negative 363c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // timeSinceLastFullSync. In this case, we don't know how long it's been since the last 364c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // full sync so we should perform the full sync. 365c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon final long timeSinceLastFullSync = SystemClock.elapsedRealtime() - 366c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon mailbox.mLastFullSyncTime; 367c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon final boolean fullSync = (uiRefresh || loadMore || 368c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon timeSinceLastFullSync >= FULL_SYNC_INTERVAL_MILLIS || timeSinceLastFullSync < 0); 369c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 370c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync) { 371c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // Find the oldest message in the local store. We need our time window to include 372c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // all messages that are currently present locally. 373c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = System.currentTimeMillis() - FULL_SYNC_WINDOW_MILLIS; 374c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Cursor localOldestCursor = null; 375c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon try { 376c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localOldestCursor = resolver.query(EmailContent.Message.CONTENT_URI, 377c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon OldestTimestampInfo.PROJECTION, 378c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.MessageColumns.ACCOUNT_KEY + "=?" + " AND " + 379c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon MessageColumns.MAILBOX_KEY + "=?", 380c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] {String.valueOf(account.mId), String.valueOf(mailbox.mId)}, 381c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon null); 382c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localOldestCursor != null && localOldestCursor.moveToFirst()) { 383c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long oldestLocalMessageDate = localOldestCursor.getLong( 384c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon OldestTimestampInfo.COLUMN_OLDEST_TIMESTAMP); 385c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (oldestLocalMessageDate > 0) { 386c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = Math.min(endDate, oldestLocalMessageDate); 387c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d( 388c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Logging.LOG_TAG, "oldest local message " + oldestLocalMessageDate); 389c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 390c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 391c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } finally { 392c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localOldestCursor != null) { 393c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localOldestCursor.close(); 394c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 3955c523858385176c33a7456bb84035de78552d22dMarc Blank } 396c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "full sync: original window: now - " + endDate); 397c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } else { 398c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // We are doing a frequent, quick sync. This only syncs a small time window, so that 399c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // we wil get any new messages, but not spend a lot of bandwidth downloading 400c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // messageIds that we most likely already have. 401c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon endDate = System.currentTimeMillis() - QUICK_SYNC_WINDOW_MILLIS; 402c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "quick sync: original window: now - " + endDate); 4035c523858385176c33a7456bb84035de78552d22dMarc Blank } 4045c523858385176c33a7456bb84035de78552d22dMarc Blank 405c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 2. Open the remote folder and create the remote folder if necessary 4065c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = Store.getInstance(account, context); 4075c523858385176c33a7456bb84035de78552d22dMarc Blank // The account might have been deleted 40835cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon if (remoteStore == null) { 40935cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon LogUtils.d(Logging.LOG_TAG, "account is apparently deleted"); 41035cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon return; 41135cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon } 412d1a87bc02d65dde9e635848531e09aadc79ff538Paul Westbrook final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 4135c523858385176c33a7456bb84035de78552d22dMarc Blank 414c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If the folder is a "special" folder we need to see if it exists 415c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // on the remote server. It if does not exist we'll try to create it. If we 416c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // can't create we'll abort. This will happen on every single Pop3 folder as 417c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // designed and on Imap folders during error conditions. This allows us 418c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // to treat Pop3 and Imap the same in this code. 419c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_SENT) { 4205c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 4215c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { 42235cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon LogUtils.w(Logging.LOG_TAG, "could not create remote folder type %d", 42335cdca3fb49840e867e133ce0ef1ed69e28b4c90Martin Hibdon mailbox.mType); 4245c523858385176c33a7456bb84035de78552d22dMarc Blank return; 4255c523858385176c33a7456bb84035de78552d22dMarc Blank } 4265c523858385176c33a7456bb84035de78552d22dMarc Blank } 4275c523858385176c33a7456bb84035de78552d22dMarc Blank } 4285c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 4295c523858385176c33a7456bb84035de78552d22dMarc Blank 430c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 3. Trash any remote messages that are marked as trashed locally. 4315c523858385176c33a7456bb84035de78552d22dMarc Blank // TODO - this comment was here, but no code was here. 4325c523858385176c33a7456bb84035de78552d22dMarc Blank 433c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 4. Get the number of messages on the server. 43417d5bbf768c27ac7782b155e2ab25bcd480f5dcfYu Ping Hu final int remoteMessageCount = remoteFolder.getMessageCount(); 43566eef4565dd0a8b99e927bed7b64c472d24c276dYu Ping Hu 436c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 5. Save folder message count locally. 43725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon mailbox.updateMessageCount(context, remoteMessageCount); 4385c523858385176c33a7456bb84035de78552d22dMarc Blank 439c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 6. Get all message Ids in our sync window: 44025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] remoteMessages; 441c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon remoteMessages = remoteFolder.getMessages(0, endDate, null); 442c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, "received " + remoteMessages.length + " messages"); 44325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 444c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 7. See if we need any additional messages beyond our date query range results. 44525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // If we do, keep increasing the size of our query window until we have 44625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // enough, or until we have all messages in the mailbox. 447c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon int totalCountNeeded; 44825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (loadMore) { 449c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = remoteMessages.length + LOAD_MORE_MIN_INCREMENT; 450c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } else { 451c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = remoteMessages.length; 452c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync && totalCountNeeded < MINIMUM_MESSAGES_TO_SYNC) { 453c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon totalCountNeeded = MINIMUM_MESSAGES_TO_SYNC; 454c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 45525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 45625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "need " + totalCountNeeded + " total"); 45725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 45825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final int additionalMessagesNeeded = totalCountNeeded - remoteMessages.length; 45925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (additionalMessagesNeeded > 0) { 46025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "trying to get " + additionalMessagesNeeded + " more"); 461c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon long startDate = endDate - 1; 46225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] additionalMessages = new Message[0]; 46325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon long windowIncreaseSize = INITIAL_WINDOW_SIZE_INCREASE; 464c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon while (additionalMessages.length < additionalMessagesNeeded && endDate > 0) { 46525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon endDate = endDate - windowIncreaseSize; 466fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon if (endDate < 0) { 467fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon LogUtils.d(Logging.LOG_TAG, "window size too large, this is the last attempt"); 468fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon endDate = 0; 469fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon } 470c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.d(Logging.LOG_TAG, 471c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon "requesting additional messages from range " + startDate + " - " + endDate); 47225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon additionalMessages = remoteFolder.getMessages(startDate, endDate, null); 47325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 47425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // If don't get enough messages with the first window size expansion, 47525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // we need to accelerate rate at which the window expands. Otherwise, 47625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // if there were no messages for several weeks, we'd always end up 47725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // performing dozens of queries. 47825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon windowIncreaseSize *= 2; 4795c523858385176c33a7456bb84035de78552d22dMarc Blank } 4805c523858385176c33a7456bb84035de78552d22dMarc Blank 48125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "additionalMessages " + additionalMessages.length); 482fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon if (additionalMessages.length < additionalMessagesNeeded) { 483fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // We have attempted to load a window that goes all the way back to time zero, 484fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // but we still don't have as many messages as the server says are in the inbox. 485fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon // This is not expected to happen. 486c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LogUtils.e(Logging.LOG_TAG, "expected to find " + additionalMessagesNeeded 487c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " more messages, only got " + additionalMessages.length); 488fda9d945e73110e644f4e953b658c248dd96355fMartin Hibdon } 48925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon int additionalToKeep = additionalMessages.length; 49025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (additionalMessages.length > LOAD_MORE_MAX_INCREMENT) { 49125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // We have way more additional messages than intended, drop some of them. 49225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // The last messages are the most recent, so those are the ones we need to keep. 49325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon additionalToKeep = LOAD_MORE_MAX_INCREMENT; 4945c523858385176c33a7456bb84035de78552d22dMarc Blank } 49525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 49625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Copy the messages into one array. 49725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message[] allMessages = new Message[remoteMessages.length + additionalToKeep]; 49825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon System.arraycopy(remoteMessages, 0, allMessages, 0, remoteMessages.length); 49925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // additionalMessages may have more than we need, only copy the last 500c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // several. These are the most recent messages in that set because 501c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // of the way IMAP server returns messages. 50225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon System.arraycopy(additionalMessages, additionalMessages.length - additionalToKeep, 50325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon allMessages, remoteMessages.length, additionalToKeep); 50425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon remoteMessages = allMessages; 5055c523858385176c33a7456bb84035de78552d22dMarc Blank } 5065c523858385176c33a7456bb84035de78552d22dMarc Blank 507c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 8. Get the all of the local messages within the sync window, and create 508c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // an index of the uids. 50966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // The IMAP query for messages ignores time, and only looks at the date part of the endDate. 51066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // So if we query for messages since Aug 11 at 3:00 PM, we can get messages from any time 51166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // on Aug 11. Our IMAP query results can include messages up to 24 hours older than endDate, 51266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // or up to 25 hours older at a daylight savings transition. 51366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // It is important that we have the Id of any local message that could potentially be 51466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // returned by the IMAP query, or we will create duplicate copies of the same messages. 51566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // So we will increase our local query range by this much. 51666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Note that this complicates deletion: It's not okay to delete anything that is in the 51766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // localMessageMap but not in the remote result, because we know that we may be getting 51866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Ids of local messages that are outside the IMAP query window. 519c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Cursor localUidCursor = null; 520c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon HashMap<String, LocalMessageInfo> localMessageMap = new HashMap<String, LocalMessageInfo>(); 521c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon try { 5220c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // FLAG: There is a problem that causes us to store the wrong date on some messages, 5230c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // so messages get a date of zero. If we filter these messages out and don't put them 5240c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // in our localMessageMap, then we'll end up loading the same message again. 5250c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon // See b/10508861 5260c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon// final long queryEndDate = endDate - DateUtils.DAY_IN_MILLIS - DateUtils.HOUR_IN_MILLIS; 5270c8df56a0aed5b97aee2031ccbce75762097b1d1Martin Hibdon final long queryEndDate = 0; 528c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localUidCursor = resolver.query( 529c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.CONTENT_URI, 530c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LocalMessageInfo.PROJECTION, 531c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.MessageColumns.ACCOUNT_KEY + "=?" 532c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " AND " + MessageColumns.MAILBOX_KEY + "=?" 533c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + " AND " + MessageColumns.TIMESTAMP + ">=?", 534c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] { 535c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon String.valueOf(account.mId), 536c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon String.valueOf(mailbox.mId), 53766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon String.valueOf(queryEndDate) }, 538c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon null); 539c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon while (localUidCursor.moveToNext()) { 540c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon LocalMessageInfo info = new LocalMessageInfo(localUidCursor); 541c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // If the message has no server id, it's local only. This should only happen for 542c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // mail created on the client that has failed to upsync. We want to ignore such 543c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // mail during synchronization (i.e. leave it as-is and let the next sync try again 544c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // to upsync). 545c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (!TextUtils.isEmpty(info.mServerId)) { 546c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localMessageMap.put(info.mServerId, info); 547c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 548c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 549c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } finally { 550c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (localUidCursor != null) { 551c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon localUidCursor.close(); 552c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 553c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 554c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 555c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 9. Get a list of the messages that are in the remote list but not on the 556c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // local store, or messages that are in the local store but failed to download 557c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // on the last sync. These are the new messages that we will download. 558c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // Note, we also skip syncing messages which are flagged as "deleted message" sentinels, 559c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // because they are locally deleted and we don't need or want the old message from 560c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // the server. 56125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final ArrayList<Message> unsyncedMessages = new ArrayList<Message>(); 56225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon final HashMap<String, Message> remoteUidMap = new HashMap<String, Message>(); 56325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Process the messages in the reverse order we received them in. This means that 564c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // we load the most recent one first, which gives a better user experience. 56525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon for (int i = remoteMessages.length - 1; i >= 0; i--) { 56625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon Message message = remoteMessages[i]; 56725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LogUtils.d(Logging.LOG_TAG, "remote message " + message.getUid()); 56825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon remoteUidMap.put(message.getUid(), message); 56925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 57025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon LocalMessageInfo localMessage = localMessageMap.get(message.getUid()); 57125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 57225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // localMessage == null -> message has never been created (not even headers) 57325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = UNLOADED -> message created, but none of body loaded 57425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = PARTIAL -> message created, a "sane" amt of body has been loaded 57525031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = COMPLETE -> message body has been completely loaded 57625031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // mFlagLoaded = DELETED -> message has been deleted 57725031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon // Only the first two of these are "unsynced", so let's retrieve them 57825031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon if (localMessage == null || 57925031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_UNLOADED) || 58025031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon (localMessage.mFlagLoaded == EmailContent.Message.FLAG_LOADED_PARTIAL)) { 58125031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon unsyncedMessages.add(message); 58225031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 58325031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon } 58425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 585c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 10. Download basic info about the new/unloaded messages (if any) 5865c523858385176c33a7456bb84035de78552d22dMarc Blank /* 5875c523858385176c33a7456bb84035de78552d22dMarc Blank * Fetch the flags and envelope only of the new messages. This is intended to get us 5885c523858385176c33a7456bb84035de78552d22dMarc Blank * critical data as fast as possible, and then we'll fill in the details. 5895c523858385176c33a7456bb84035de78552d22dMarc Blank */ 5905c523858385176c33a7456bb84035de78552d22dMarc Blank if (unsyncedMessages.size() > 0) { 5915c523858385176c33a7456bb84035de78552d22dMarc Blank downloadFlagAndEnvelope(context, account, mailbox, remoteFolder, unsyncedMessages, 5925c523858385176c33a7456bb84035de78552d22dMarc Blank localMessageMap, unseenMessages); 5935c523858385176c33a7456bb84035de78552d22dMarc Blank } 5945c523858385176c33a7456bb84035de78552d22dMarc Blank 595c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 11. Refresh the flags for any messages in the local store that we 596c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // didn't just download. 5975c523858385176c33a7456bb84035de78552d22dMarc Blank FetchProfile fp = new FetchProfile(); 5985c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.FLAGS); 5995c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.fetch(remoteMessages, fp, null); 6005c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsSeen = false; 6015c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsFlagged = false; 6025c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSupportsAnswered = false; 6035c523858385176c33a7456bb84035de78552d22dMarc Blank for (Flag flag : remoteFolder.getPermanentFlags()) { 6045c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.SEEN) { 6055c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsSeen = true; 6065c523858385176c33a7456bb84035de78552d22dMarc Blank } 6075c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.FLAGGED) { 6085c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsFlagged = true; 6095c523858385176c33a7456bb84035de78552d22dMarc Blank } 6105c523858385176c33a7456bb84035de78552d22dMarc Blank if (flag == Flag.ANSWERED) { 6115c523858385176c33a7456bb84035de78552d22dMarc Blank remoteSupportsAnswered = true; 6125c523858385176c33a7456bb84035de78552d22dMarc Blank } 6135c523858385176c33a7456bb84035de78552d22dMarc Blank } 61425031796061b1d87cf2f38cdb34110c2291fbd47Martin Hibdon 615c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 12. Update SEEN/FLAGGED/ANSWERED (star) flags (if supported remotely - e.g. not for POP3) 6165c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteSupportsSeen || remoteSupportsFlagged || remoteSupportsAnswered) { 6175c523858385176c33a7456bb84035de78552d22dMarc Blank for (Message remoteMessage : remoteMessages) { 6185c523858385176c33a7456bb84035de78552d22dMarc Blank LocalMessageInfo localMessageInfo = localMessageMap.get(remoteMessage.getUid()); 6195c523858385176c33a7456bb84035de78552d22dMarc Blank if (localMessageInfo == null) { 6205c523858385176c33a7456bb84035de78552d22dMarc Blank continue; 6215c523858385176c33a7456bb84035de78552d22dMarc Blank } 6225c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localSeen = localMessageInfo.mFlagRead; 6235c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteSeen = remoteMessage.isSet(Flag.SEEN); 6245c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newSeen = (remoteSupportsSeen && (remoteSeen != localSeen)); 6255c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localFlagged = localMessageInfo.mFlagFavorite; 6265c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteFlagged = remoteMessage.isSet(Flag.FLAGGED); 6275c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newFlagged = (remoteSupportsFlagged && (localFlagged != remoteFlagged)); 6285c523858385176c33a7456bb84035de78552d22dMarc Blank int localFlags = localMessageInfo.mFlags; 6295c523858385176c33a7456bb84035de78552d22dMarc Blank boolean localAnswered = (localFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0; 6305c523858385176c33a7456bb84035de78552d22dMarc Blank boolean remoteAnswered = remoteMessage.isSet(Flag.ANSWERED); 6315c523858385176c33a7456bb84035de78552d22dMarc Blank boolean newAnswered = (remoteSupportsAnswered && (localAnswered != remoteAnswered)); 6325c523858385176c33a7456bb84035de78552d22dMarc Blank if (newSeen || newFlagged || newAnswered) { 6335c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId( 6345c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_URI, localMessageInfo.mId); 6355c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues updateValues = new ContentValues(); 6365c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAG_READ, remoteSeen); 6375c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAG_FAVORITE, remoteFlagged); 6385c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteAnswered) { 6395c523858385176c33a7456bb84035de78552d22dMarc Blank localFlags |= EmailContent.Message.FLAG_REPLIED_TO; 6405c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 6415c523858385176c33a7456bb84035de78552d22dMarc Blank localFlags &= ~EmailContent.Message.FLAG_REPLIED_TO; 6425c523858385176c33a7456bb84035de78552d22dMarc Blank } 6435c523858385176c33a7456bb84035de78552d22dMarc Blank updateValues.put(MessageColumns.FLAGS, localFlags); 6445c523858385176c33a7456bb84035de78552d22dMarc Blank resolver.update(uri, updateValues, null, null); 6455c523858385176c33a7456bb84035de78552d22dMarc Blank } 6465c523858385176c33a7456bb84035de78552d22dMarc Blank } 6475c523858385176c33a7456bb84035de78552d22dMarc Blank } 6485c523858385176c33a7456bb84035de78552d22dMarc Blank 649c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 13. Remove messages that are in the local store and in the current sync window, 65066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // but no longer on the remote store. Note that localMessageMap can contain messages 65166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // that are not actually in our sync window. We need to check the timestamp to ensure 65266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // that it is before deleting. 65366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon for (final LocalMessageInfo info : localMessageMap.values()) { 65466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // If this message is inside our sync window, and we cannot find it in our list 65566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // of remote messages, then we know it's been deleted from the server. 65666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon if (info.mTimestamp >= endDate && !remoteUidMap.containsKey(info.mServerId)) { 65766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete associated data (attachment files) 65866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Attachment & Body records are auto-deleted when we delete the Message record 65966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, info.mId); 66066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon 66166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete the message itself 66266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri uriToDelete = ContentUris.withAppendedId( 66366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.CONTENT_URI, info.mId); 66466ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(uriToDelete, null, null); 66566ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon 66666ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon // Delete extra rows (e.g. synced or deleted) 66766ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri syncRowToDelete = ContentUris.withAppendedId( 66866ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.UPDATED_CONTENT_URI, info.mId); 66966ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(syncRowToDelete, null, null); 67066ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon Uri deletERowToDelete = ContentUris.withAppendedId( 67166ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon EmailContent.Message.UPDATED_CONTENT_URI, info.mId); 67266ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon resolver.delete(deletERowToDelete, null, null); 67366ac290b353302256e85d319c0ce623277dfdad3Martin Hibdon } 6745c523858385176c33a7456bb84035de78552d22dMarc Blank } 6755c523858385176c33a7456bb84035de78552d22dMarc Blank 6765c523858385176c33a7456bb84035de78552d22dMarc Blank loadUnsyncedMessages(context, account, remoteFolder, unsyncedMessages, mailbox); 6775c523858385176c33a7456bb84035de78552d22dMarc Blank 678c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon if (fullSync) { 679c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon mailbox.updateLastFullSyncTime(context, SystemClock.elapsedRealtime()); 680c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 681c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 682c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 14. Clean up and report results 6835c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 6845c523858385176c33a7456bb84035de78552d22dMarc Blank } 6855c523858385176c33a7456bb84035de78552d22dMarc Blank 6865c523858385176c33a7456bb84035de78552d22dMarc Blank /** 6875c523858385176c33a7456bb84035de78552d22dMarc Blank * Find messages in the updated table that need to be written back to server. 6885c523858385176c33a7456bb84035de78552d22dMarc Blank * 6895c523858385176c33a7456bb84035de78552d22dMarc Blank * Handles: 6905c523858385176c33a7456bb84035de78552d22dMarc Blank * Read/Unread 6915c523858385176c33a7456bb84035de78552d22dMarc Blank * Flagged 6925c523858385176c33a7456bb84035de78552d22dMarc Blank * Append (upload) 6935c523858385176c33a7456bb84035de78552d22dMarc Blank * Move To Trash 6945c523858385176c33a7456bb84035de78552d22dMarc Blank * Empty trash 6955c523858385176c33a7456bb84035de78552d22dMarc Blank * TODO: 6965c523858385176c33a7456bb84035de78552d22dMarc Blank * Move 6975c523858385176c33a7456bb84035de78552d22dMarc Blank * 6985c523858385176c33a7456bb84035de78552d22dMarc Blank * @param account the account to scan for pending actions 6995c523858385176c33a7456bb84035de78552d22dMarc Blank * @throws MessagingException 7005c523858385176c33a7456bb84035de78552d22dMarc Blank */ 7015c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingActionsSynchronous(Context context, Account account) 702c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon throws MessagingException { 7035c523858385176c33a7456bb84035de78552d22dMarc Blank TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(context, account)); 7045c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs = new String[] { Long.toString(account.mId) }; 7055c523858385176c33a7456bb84035de78552d22dMarc Blank 7065c523858385176c33a7456bb84035de78552d22dMarc Blank // Handle deletes first, it's always better to get rid of things first 7075c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingDeletesSynchronous(context, account, accountIdArgs); 7085c523858385176c33a7456bb84035de78552d22dMarc Blank 7095c523858385176c33a7456bb84035de78552d22dMarc Blank // Handle uploads (currently, only to sent messages) 7105c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingUploadsSynchronous(context, account, accountIdArgs); 7115c523858385176c33a7456bb84035de78552d22dMarc Blank 7125c523858385176c33a7456bb84035de78552d22dMarc Blank // Now handle updates / upsyncs 7135c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingUpdatesSynchronous(context, account, accountIdArgs); 7145c523858385176c33a7456bb84035de78552d22dMarc Blank } 7155c523858385176c33a7456bb84035de78552d22dMarc Blank 7165c523858385176c33a7456bb84035de78552d22dMarc Blank /** 7175c523858385176c33a7456bb84035de78552d22dMarc Blank * Get the mailbox corresponding to the remote location of a message; this will normally be 7185c523858385176c33a7456bb84035de78552d22dMarc Blank * the mailbox whose _id is mailboxKey, except for search results, where we must look it up 719c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * by serverId. 720c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * 7215c523858385176c33a7456bb84035de78552d22dMarc Blank * @param message the message in question 7225c523858385176c33a7456bb84035de78552d22dMarc Blank * @return the mailbox in which the message resides on the server 7235c523858385176c33a7456bb84035de78552d22dMarc Blank */ 724c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon private static Mailbox getRemoteMailboxForMessage( 725c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Context context, EmailContent.Message message) { 7265c523858385176c33a7456bb84035de78552d22dMarc Blank // If this is a search result, use the protocolSearchInfo field to get the server info 7275c523858385176c33a7456bb84035de78552d22dMarc Blank if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) { 7285c523858385176c33a7456bb84035de78552d22dMarc Blank long accountKey = message.mAccountKey; 7295c523858385176c33a7456bb84035de78552d22dMarc Blank String protocolSearchInfo = message.mProtocolSearchInfo; 7305c523858385176c33a7456bb84035de78552d22dMarc Blank if (accountKey == mLastSearchAccountKey && 7315c523858385176c33a7456bb84035de78552d22dMarc Blank protocolSearchInfo.equals(mLastSearchServerId)) { 7325c523858385176c33a7456bb84035de78552d22dMarc Blank return mLastSearchRemoteMailbox; 7335c523858385176c33a7456bb84035de78552d22dMarc Blank } 73417d5bbf768c27ac7782b155e2ab25bcd480f5dcfYu Ping Hu Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, 7355c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox.CONTENT_PROJECTION, Mailbox.PATH_AND_ACCOUNT_SELECTION, 736c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon new String[] {protocolSearchInfo, Long.toString(accountKey) }, 7375c523858385176c33a7456bb84035de78552d22dMarc Blank null); 7385c523858385176c33a7456bb84035de78552d22dMarc Blank try { 7395c523858385176c33a7456bb84035de78552d22dMarc Blank if (c.moveToNext()) { 7405c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = new Mailbox(); 7415c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox.restore(c); 7425c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchAccountKey = accountKey; 7435c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchServerId = protocolSearchInfo; 7445c523858385176c33a7456bb84035de78552d22dMarc Blank mLastSearchRemoteMailbox = mailbox; 7455c523858385176c33a7456bb84035de78552d22dMarc Blank return mailbox; 7465c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 7475c523858385176c33a7456bb84035de78552d22dMarc Blank return null; 7485c523858385176c33a7456bb84035de78552d22dMarc Blank } 7495c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 7505c523858385176c33a7456bb84035de78552d22dMarc Blank c.close(); 7515c523858385176c33a7456bb84035de78552d22dMarc Blank } 7525c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 7535c523858385176c33a7456bb84035de78552d22dMarc Blank return Mailbox.restoreMailboxWithId(context, message.mMailboxKey); 7545c523858385176c33a7456bb84035de78552d22dMarc Blank } 7555c523858385176c33a7456bb84035de78552d22dMarc Blank } 7565c523858385176c33a7456bb84035de78552d22dMarc Blank 7575c523858385176c33a7456bb84035de78552d22dMarc Blank /** 7585c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in the Message_Deletes table, look for differences that 7595c523858385176c33a7456bb84035de78552d22dMarc Blank * we can deal with, and do the work. 7605c523858385176c33a7456bb84035de78552d22dMarc Blank */ 7615c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingDeletesSynchronous(Context context, Account account, 7625c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 7635c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor deletes = context.getContentResolver().query( 7645c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.DELETED_CONTENT_URI, 7655c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_PROJECTION, 7665c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs, 7675c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.MAILBOX_KEY); 7685c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 7695c523858385176c33a7456bb84035de78552d22dMarc Blank try { 7705c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 7715c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 7725c523858385176c33a7456bb84035de78552d22dMarc Blank // loop through messages marked as deleted 7735c523858385176c33a7456bb84035de78552d22dMarc Blank while (deletes.moveToNext()) { 7745c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message oldMessage = 7755c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.getContent(deletes, EmailContent.Message.class); 7765c523858385176c33a7456bb84035de78552d22dMarc Blank 7775c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMessage != null) { 7785c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = oldMessage.mId; 7795c523858385176c33a7456bb84035de78552d22dMarc Blank 7805c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = getRemoteMailboxForMessage(context, oldMessage); 7815c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 7825c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 7835c523858385176c33a7456bb84035de78552d22dMarc Blank } 7847d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler final boolean deleteFromTrash = mailbox.mType == Mailbox.TYPE_TRASH; 7855c523858385176c33a7456bb84035de78552d22dMarc Blank 7865c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 7875c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null && deleteFromTrash) { 7885c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 7895c523858385176c33a7456bb84035de78552d22dMarc Blank } 7905c523858385176c33a7456bb84035de78552d22dMarc Blank 7915c523858385176c33a7456bb84035de78552d22dMarc Blank // Dispatch here for specific change types 7925c523858385176c33a7456bb84035de78552d22dMarc Blank if (deleteFromTrash) { 7935c523858385176c33a7456bb84035de78552d22dMarc Blank // Move message to trash 7947d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processPendingDeleteFromTrash(remoteStore, mailbox, oldMessage); 7955c523858385176c33a7456bb84035de78552d22dMarc Blank } 7965c523858385176c33a7456bb84035de78552d22dMarc Blank 7977d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler // Finally, delete the update 7987d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Uri uri = ContentUris.withAppendedId(EmailContent.Message.DELETED_CONTENT_URI, 7997d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler oldMessage.mId); 8007d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler context.getContentResolver().delete(uri, null, null); 8017d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler } 8025c523858385176c33a7456bb84035de78552d22dMarc Blank } 8035c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 8045c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 8055c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 8065c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 807560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending delete for id=" 808c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + lastMessageId + ": " + me); 8095c523858385176c33a7456bb84035de78552d22dMarc Blank } 8105c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8115c523858385176c33a7456bb84035de78552d22dMarc Blank deletes.close(); 8125c523858385176c33a7456bb84035de78552d22dMarc Blank } 8135c523858385176c33a7456bb84035de78552d22dMarc Blank } 8145c523858385176c33a7456bb84035de78552d22dMarc Blank 8155c523858385176c33a7456bb84035de78552d22dMarc Blank /** 8165c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in Sent, and are in need of upload, 817c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * and send them to the server. "In need of upload" is defined as: 8185c523858385176c33a7456bb84035de78552d22dMarc Blank * serverId == null (no UID has been assigned) 8195c523858385176c33a7456bb84035de78552d22dMarc Blank * or 8205c523858385176c33a7456bb84035de78552d22dMarc Blank * message is in the updated list 8215c523858385176c33a7456bb84035de78552d22dMarc Blank * 822c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Note we also look for messages that are moving from drafts->outbox->sent. They never 8235c523858385176c33a7456bb84035de78552d22dMarc Blank * go through "drafts" or "outbox" on the server, so we hang onto these until they can be 8245c523858385176c33a7456bb84035de78552d22dMarc Blank * uploaded directly to the Sent folder. 8255c523858385176c33a7456bb84035de78552d22dMarc Blank */ 8265c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingUploadsSynchronous(Context context, Account account, 8275c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 8285c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 8295c523858385176c33a7456bb84035de78552d22dMarc Blank // Find the Sent folder (since that's all we're uploading for now 830c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // TODO: Upsync for all folders? (In case a user moves mail from Sent before it is 831c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // handled. Also, this would generically solve allowing drafts to upload.) 8325c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor mailboxes = resolver.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, 8335c523858385176c33a7456bb84035de78552d22dMarc Blank MailboxColumns.ACCOUNT_KEY + "=?" 8345c523858385176c33a7456bb84035de78552d22dMarc Blank + " and " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_SENT, 8355c523858385176c33a7456bb84035de78552d22dMarc Blank accountIdArgs, null); 8365c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 8375c523858385176c33a7456bb84035de78552d22dMarc Blank try { 8385c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 8395c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 8405c523858385176c33a7456bb84035de78552d22dMarc Blank while (mailboxes.moveToNext()) { 8415c523858385176c33a7456bb84035de78552d22dMarc Blank long mailboxId = mailboxes.getLong(Mailbox.ID_PROJECTION_COLUMN); 8425c523858385176c33a7456bb84035de78552d22dMarc Blank String[] mailboxKeyArgs = new String[] { Long.toString(mailboxId) }; 8435c523858385176c33a7456bb84035de78552d22dMarc Blank // Demand load mailbox 8445c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = null; 8455c523858385176c33a7456bb84035de78552d22dMarc Blank 8465c523858385176c33a7456bb84035de78552d22dMarc Blank // First handle the "new" messages (serverId == null) 8475c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor upsyncs1 = resolver.query(EmailContent.Message.CONTENT_URI, 8485c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.ID_PROJECTION, 8495c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.MAILBOX_KEY + "=?" 8505c523858385176c33a7456bb84035de78552d22dMarc Blank + " and (" + EmailContent.Message.SERVER_ID + " is null" 8515c523858385176c33a7456bb84035de78552d22dMarc Blank + " or " + EmailContent.Message.SERVER_ID + "=''" + ")", 8525c523858385176c33a7456bb84035de78552d22dMarc Blank mailboxKeyArgs, 8535c523858385176c33a7456bb84035de78552d22dMarc Blank null); 8545c523858385176c33a7456bb84035de78552d22dMarc Blank try { 8555c523858385176c33a7456bb84035de78552d22dMarc Blank while (upsyncs1.moveToNext()) { 8565c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 8575c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null) { 8585c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 8595c523858385176c33a7456bb84035de78552d22dMarc Blank } 8605c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the mailbox if it will be needed 8615c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 8625c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 8635c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 8645c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 8655c523858385176c33a7456bb84035de78552d22dMarc Blank } 8665c523858385176c33a7456bb84035de78552d22dMarc Blank } 8675c523858385176c33a7456bb84035de78552d22dMarc Blank // upsync the message 8685c523858385176c33a7456bb84035de78552d22dMarc Blank long id = upsyncs1.getLong(EmailContent.Message.ID_PROJECTION_COLUMN); 8695c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = id; 8707d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processUploadMessage(context, remoteStore, mailbox, id); 8715c523858385176c33a7456bb84035de78552d22dMarc Blank } 8725c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8735c523858385176c33a7456bb84035de78552d22dMarc Blank if (upsyncs1 != null) { 8745c523858385176c33a7456bb84035de78552d22dMarc Blank upsyncs1.close(); 8755c523858385176c33a7456bb84035de78552d22dMarc Blank } 8765c523858385176c33a7456bb84035de78552d22dMarc Blank } 8775c523858385176c33a7456bb84035de78552d22dMarc Blank } 8785c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 8795c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 8805c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 8815c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 882560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending upsync for id=" 8835c523858385176c33a7456bb84035de78552d22dMarc Blank + lastMessageId + ": " + me); 8845c523858385176c33a7456bb84035de78552d22dMarc Blank } 8855c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 8865c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailboxes != null) { 8875c523858385176c33a7456bb84035de78552d22dMarc Blank mailboxes.close(); 8885c523858385176c33a7456bb84035de78552d22dMarc Blank } 8895c523858385176c33a7456bb84035de78552d22dMarc Blank } 8905c523858385176c33a7456bb84035de78552d22dMarc Blank } 8915c523858385176c33a7456bb84035de78552d22dMarc Blank 8925c523858385176c33a7456bb84035de78552d22dMarc Blank /** 8935c523858385176c33a7456bb84035de78552d22dMarc Blank * Scan for messages that are in the Message_Updates table, look for differences that 8945c523858385176c33a7456bb84035de78552d22dMarc Blank * we can deal with, and do the work. 8955c523858385176c33a7456bb84035de78552d22dMarc Blank */ 8965c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingUpdatesSynchronous(Context context, Account account, 8975c523858385176c33a7456bb84035de78552d22dMarc Blank String[] accountIdArgs) { 8985c523858385176c33a7456bb84035de78552d22dMarc Blank ContentResolver resolver = context.getContentResolver(); 8995c523858385176c33a7456bb84035de78552d22dMarc Blank Cursor updates = resolver.query(EmailContent.Message.UPDATED_CONTENT_URI, 9005c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_PROJECTION, 9015c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.ACCOUNT_KEY + "=?", accountIdArgs, 9025c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.MessageColumns.MAILBOX_KEY); 9035c523858385176c33a7456bb84035de78552d22dMarc Blank long lastMessageId = -1; 9045c523858385176c33a7456bb84035de78552d22dMarc Blank try { 9055c523858385176c33a7456bb84035de78552d22dMarc Blank // Defer setting up the store until we know we need to access it 9065c523858385176c33a7456bb84035de78552d22dMarc Blank Store remoteStore = null; 9075c523858385176c33a7456bb84035de78552d22dMarc Blank // Demand load mailbox (note order-by to reduce thrashing here) 9085c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox = null; 9095c523858385176c33a7456bb84035de78552d22dMarc Blank // loop through messages marked as needing updates 9105c523858385176c33a7456bb84035de78552d22dMarc Blank while (updates.moveToNext()) { 9115c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeMoveToTrash = false; 9125c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeRead = false; 9135c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeFlagged = false; 9145c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeMailbox = false; 9155c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeAnswered = false; 9165c523858385176c33a7456bb84035de78552d22dMarc Blank 9175c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message oldMessage = 918c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.getContent(updates, EmailContent.Message.class); 9195c523858385176c33a7456bb84035de78552d22dMarc Blank lastMessageId = oldMessage.mId; 9205c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message newMessage = 921c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.restoreMessageWithId(context, oldMessage.mId); 9225c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage != null) { 9235c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = Mailbox.restoreMailboxWithId(context, newMessage.mMailboxKey); 9245c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox == null) { 9255c523858385176c33a7456bb84035de78552d22dMarc Blank continue; // Mailbox removed. Move to the next message. 9265c523858385176c33a7456bb84035de78552d22dMarc Blank } 9275c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMessage.mMailboxKey != newMessage.mMailboxKey) { 9285c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_TRASH) { 9295c523858385176c33a7456bb84035de78552d22dMarc Blank changeMoveToTrash = true; 9305c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 9315c523858385176c33a7456bb84035de78552d22dMarc Blank changeMailbox = true; 9325c523858385176c33a7456bb84035de78552d22dMarc Blank } 9335c523858385176c33a7456bb84035de78552d22dMarc Blank } 9345c523858385176c33a7456bb84035de78552d22dMarc Blank changeRead = oldMessage.mFlagRead != newMessage.mFlagRead; 9355c523858385176c33a7456bb84035de78552d22dMarc Blank changeFlagged = oldMessage.mFlagFavorite != newMessage.mFlagFavorite; 9365c523858385176c33a7456bb84035de78552d22dMarc Blank changeAnswered = (oldMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 937c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO); 938c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon } 9395c523858385176c33a7456bb84035de78552d22dMarc Blank 9405c523858385176c33a7456bb84035de78552d22dMarc Blank // Load the remote store if it will be needed 9415c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteStore == null && 9425c523858385176c33a7456bb84035de78552d22dMarc Blank (changeMoveToTrash || changeRead || changeFlagged || changeMailbox || 9435c523858385176c33a7456bb84035de78552d22dMarc Blank changeAnswered)) { 9445c523858385176c33a7456bb84035de78552d22dMarc Blank remoteStore = Store.getInstance(account, context); 9455c523858385176c33a7456bb84035de78552d22dMarc Blank } 9465c523858385176c33a7456bb84035de78552d22dMarc Blank 9475c523858385176c33a7456bb84035de78552d22dMarc Blank // Dispatch here for specific change types 9485c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeMoveToTrash) { 9495c523858385176c33a7456bb84035de78552d22dMarc Blank // Move message to trash 9507d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler processPendingMoveToTrash(context, remoteStore, mailbox, oldMessage, 9515c523858385176c33a7456bb84035de78552d22dMarc Blank newMessage); 9525c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (changeRead || changeFlagged || changeMailbox || changeAnswered) { 9535c523858385176c33a7456bb84035de78552d22dMarc Blank processPendingDataChange(context, remoteStore, mailbox, changeRead, 9545c523858385176c33a7456bb84035de78552d22dMarc Blank changeFlagged, changeMailbox, changeAnswered, oldMessage, newMessage); 9555c523858385176c33a7456bb84035de78552d22dMarc Blank } 9565c523858385176c33a7456bb84035de78552d22dMarc Blank 9575c523858385176c33a7456bb84035de78552d22dMarc Blank // Finally, delete the update 9585c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId(EmailContent.Message.UPDATED_CONTENT_URI, 9595c523858385176c33a7456bb84035de78552d22dMarc Blank oldMessage.mId); 9605c523858385176c33a7456bb84035de78552d22dMarc Blank resolver.delete(uri, null, null); 9615c523858385176c33a7456bb84035de78552d22dMarc Blank } 9625c523858385176c33a7456bb84035de78552d22dMarc Blank 9635c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 9645c523858385176c33a7456bb84035de78552d22dMarc Blank // Presumably an error here is an account connection failure, so there is 9655c523858385176c33a7456bb84035de78552d22dMarc Blank // no point in continuing through the rest of the pending updates. 9665c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 967560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Unable to process pending update for id=" 968c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon + lastMessageId + ": " + me); 9695c523858385176c33a7456bb84035de78552d22dMarc Blank } 9705c523858385176c33a7456bb84035de78552d22dMarc Blank } finally { 9715c523858385176c33a7456bb84035de78552d22dMarc Blank updates.close(); 9725c523858385176c33a7456bb84035de78552d22dMarc Blank } 9735c523858385176c33a7456bb84035de78552d22dMarc Blank } 9745c523858385176c33a7456bb84035de78552d22dMarc Blank 9755c523858385176c33a7456bb84035de78552d22dMarc Blank /** 976c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Upsync an entire message. This must also unwind whatever triggered it (either by 9775c523858385176c33a7456bb84035de78552d22dMarc Blank * updating the serverId, or by deleting the update record, or it's going to keep happening 9785c523858385176c33a7456bb84035de78552d22dMarc Blank * over and over again. 9795c523858385176c33a7456bb84035de78552d22dMarc Blank * 980c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * Note: If the message is being uploaded into an unexpected mailbox, we *do not* upload. 981c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * This is to avoid unnecessary uploads into the trash. Although the caller attempts to select 9825c523858385176c33a7456bb84035de78552d22dMarc Blank * only the Drafts and Sent folders, this can happen when the update record and the current 983c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon * record mismatch. In this case, we let the update record remain, because the filters 9845c523858385176c33a7456bb84035de78552d22dMarc Blank * in processPendingUpdatesSynchronous() will pick it up as a move and handle it (or drop it) 9855c523858385176c33a7456bb84035de78552d22dMarc Blank * appropriately. 9865c523858385176c33a7456bb84035de78552d22dMarc Blank * 9875c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the actual mailbox 9885c523858385176c33a7456bb84035de78552d22dMarc Blank */ 9897d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private static void processUploadMessage(Context context, Store remoteStore, Mailbox mailbox, 9907d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler long messageId) 9915c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 9925c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message newMessage = 993c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon EmailContent.Message.restoreMessageWithId(context, messageId); 994c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu final boolean deleteUpdate; 9955c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage == null) { 9965c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = true; 997560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync failed for null message, id=" + messageId); 9985c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_DRAFTS) { 9995c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1000560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=drafts, id=" + messageId); 10015c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_OUTBOX) { 10025c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1003560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=outbox, id=" + messageId); 10045c523858385176c33a7456bb84035de78552d22dMarc Blank } else if (mailbox.mType == Mailbox.TYPE_TRASH) { 10055c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1006560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped for mailbox=trash, id=" + messageId); 10071b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy } else if (newMessage.mMailboxKey != mailbox.mId) { 10085c523858385176c33a7456bb84035de78552d22dMarc Blank deleteUpdate = false; 1009560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsync skipped; mailbox changed, id=" + messageId); 10105c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 1011560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Upsyc triggered for message id=" + messageId); 10127d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler deleteUpdate = processPendingAppend(context, remoteStore, mailbox, newMessage); 10135c523858385176c33a7456bb84035de78552d22dMarc Blank } 10145c523858385176c33a7456bb84035de78552d22dMarc Blank if (deleteUpdate) { 10155c523858385176c33a7456bb84035de78552d22dMarc Blank // Finally, delete the update (if any) 10165c523858385176c33a7456bb84035de78552d22dMarc Blank Uri uri = ContentUris.withAppendedId( 10175c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.UPDATED_CONTENT_URI, messageId); 10185c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().delete(uri, null, null); 10195c523858385176c33a7456bb84035de78552d22dMarc Blank } 10205c523858385176c33a7456bb84035de78552d22dMarc Blank } 10215c523858385176c33a7456bb84035de78552d22dMarc Blank 10225c523858385176c33a7456bb84035de78552d22dMarc Blank /** 10235c523858385176c33a7456bb84035de78552d22dMarc Blank * Upsync changes to read, flagged, or mailbox 10245c523858385176c33a7456bb84035de78552d22dMarc Blank * 10255c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store for this mailbox 10265c523858385176c33a7456bb84035de78552d22dMarc Blank * @param mailbox the mailbox the message is stored in 10275c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeRead whether the message's read state has changed 10285c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeFlagged whether the message's flagged state has changed 10295c523858385176c33a7456bb84035de78552d22dMarc Blank * @param changeMailbox whether the message's mailbox has changed 10305c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage the message in it's pre-change state 10315c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMessage the current version of the message 10325c523858385176c33a7456bb84035de78552d22dMarc Blank */ 10335c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingDataChange(final Context context, Store remoteStore, 10345c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox mailbox, boolean changeRead, boolean changeFlagged, boolean changeMailbox, 10355c523858385176c33a7456bb84035de78552d22dMarc Blank boolean changeAnswered, EmailContent.Message oldMessage, 10365c523858385176c33a7456bb84035de78552d22dMarc Blank final EmailContent.Message newMessage) throws MessagingException { 10375c523858385176c33a7456bb84035de78552d22dMarc Blank // New mailbox is the mailbox this message WILL be in (same as the one it WAS in if it isn't 10385c523858385176c33a7456bb84035de78552d22dMarc Blank // being moved 10395c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox newMailbox = mailbox; 10405c523858385176c33a7456bb84035de78552d22dMarc Blank // Mailbox is the original remote mailbox (the one we're acting on) 10415c523858385176c33a7456bb84035de78552d22dMarc Blank mailbox = getRemoteMailboxForMessage(context, oldMessage); 10425c523858385176c33a7456bb84035de78552d22dMarc Blank 10435c523858385176c33a7456bb84035de78552d22dMarc Blank // 0. No remote update if the message is local-only 10445c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage.mServerId == null || newMessage.mServerId.equals("") 10455c523858385176c33a7456bb84035de78552d22dMarc Blank || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX) || (mailbox == null)) { 10465c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10475c523858385176c33a7456bb84035de78552d22dMarc Blank } 10485c523858385176c33a7456bb84035de78552d22dMarc Blank 10495c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. No remote update for DRAFTS or OUTBOX 10505c523858385176c33a7456bb84035de78552d22dMarc Blank if (mailbox.mType == Mailbox.TYPE_DRAFTS || mailbox.mType == Mailbox.TYPE_OUTBOX) { 10515c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10525c523858385176c33a7456bb84035de78552d22dMarc Blank } 10535c523858385176c33a7456bb84035de78552d22dMarc Blank 10545c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. Open the remote store & folder 10555c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 10565c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 10575c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10585c523858385176c33a7456bb84035de78552d22dMarc Blank } 10595c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 10605c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 10615c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10625c523858385176c33a7456bb84035de78552d22dMarc Blank } 10635c523858385176c33a7456bb84035de78552d22dMarc Blank 10645c523858385176c33a7456bb84035de78552d22dMarc Blank // 3. Finally, apply the changes to the message 10655c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteFolder.getMessage(newMessage.mServerId); 10665c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 10675c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10685c523858385176c33a7456bb84035de78552d22dMarc Blank } 10695c523858385176c33a7456bb84035de78552d22dMarc Blank if (MailActivityEmail.DEBUG) { 1070560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, 10715c523858385176c33a7456bb84035de78552d22dMarc Blank "Update for msg id=" + newMessage.mId 10725c523858385176c33a7456bb84035de78552d22dMarc Blank + " read=" + newMessage.mFlagRead 10735c523858385176c33a7456bb84035de78552d22dMarc Blank + " flagged=" + newMessage.mFlagFavorite 10745c523858385176c33a7456bb84035de78552d22dMarc Blank + " answered=" 10755c523858385176c33a7456bb84035de78552d22dMarc Blank + ((newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0) 10765c523858385176c33a7456bb84035de78552d22dMarc Blank + " new mailbox=" + newMessage.mMailboxKey); 10775c523858385176c33a7456bb84035de78552d22dMarc Blank } 10785c523858385176c33a7456bb84035de78552d22dMarc Blank Message[] messages = new Message[] { remoteMessage }; 10795c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeRead) { 10805c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_SEEN, newMessage.mFlagRead); 10815c523858385176c33a7456bb84035de78552d22dMarc Blank } 10825c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeFlagged) { 10835c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_FLAGGED, newMessage.mFlagFavorite); 10845c523858385176c33a7456bb84035de78552d22dMarc Blank } 10855c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeAnswered) { 10865c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.setFlags(messages, FLAG_LIST_ANSWERED, 10875c523858385176c33a7456bb84035de78552d22dMarc Blank (newMessage.mFlags & EmailContent.Message.FLAG_REPLIED_TO) != 0); 10885c523858385176c33a7456bb84035de78552d22dMarc Blank } 10895c523858385176c33a7456bb84035de78552d22dMarc Blank if (changeMailbox) { 10905c523858385176c33a7456bb84035de78552d22dMarc Blank Folder toFolder = remoteStore.getFolder(newMailbox.mServerId); 10915c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 10925c523858385176c33a7456bb84035de78552d22dMarc Blank return; 10935c523858385176c33a7456bb84035de78552d22dMarc Blank } 10945c523858385176c33a7456bb84035de78552d22dMarc Blank // We may need the message id to search for the message in the destination folder 10955c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setMessageId(newMessage.mMessageId); 10965c523858385176c33a7456bb84035de78552d22dMarc Blank // Copy the message to its new folder 10975c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.copyMessages(messages, toFolder, new MessageUpdateCallbacks() { 10985c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 10995c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageUidChange(Message message, String newUid) { 11005c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues cv = new ContentValues(); 11015c523858385176c33a7456bb84035de78552d22dMarc Blank cv.put(EmailContent.Message.SERVER_ID, newUid); 11025c523858385176c33a7456bb84035de78552d22dMarc Blank // We only have one message, so, any updates _must_ be for it. Otherwise, 11035c523858385176c33a7456bb84035de78552d22dMarc Blank // we'd have to cycle through to find the one with the same server ID. 11045c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().update(ContentUris.withAppendedId( 11055c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message.CONTENT_URI, newMessage.mId), cv, null, null); 11065c523858385176c33a7456bb84035de78552d22dMarc Blank } 1107c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon 11085c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 11095c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageNotFound(Message message) { 11105c523858385176c33a7456bb84035de78552d22dMarc Blank } 11115c523858385176c33a7456bb84035de78552d22dMarc Blank }); 11125c523858385176c33a7456bb84035de78552d22dMarc Blank // Delete the message from the remote source folder 11135c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 11145c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.expunge(); 11155c523858385176c33a7456bb84035de78552d22dMarc Blank } 11165c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11175c523858385176c33a7456bb84035de78552d22dMarc Blank } 11185c523858385176c33a7456bb84035de78552d22dMarc Blank 11195c523858385176c33a7456bb84035de78552d22dMarc Blank /** 11205c523858385176c33a7456bb84035de78552d22dMarc Blank * Process a pending trash message command. 11215c523858385176c33a7456bb84035de78552d22dMarc Blank * 11225c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store we're working in 11235c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMailbox The local trash mailbox 11245c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage The message copy that was saved in the updates shadow table 11255c523858385176c33a7456bb84035de78552d22dMarc Blank * @param newMessage The message that was moved to the mailbox 11265c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11275c523858385176c33a7456bb84035de78552d22dMarc Blank private static void processPendingMoveToTrash(final Context context, Store remoteStore, 11287d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Mailbox newMailbox, EmailContent.Message oldMessage, 11295c523858385176c33a7456bb84035de78552d22dMarc Blank final EmailContent.Message newMessage) throws MessagingException { 11305c523858385176c33a7456bb84035de78552d22dMarc Blank 11315c523858385176c33a7456bb84035de78552d22dMarc Blank // 0. No remote move if the message is local-only 11325c523858385176c33a7456bb84035de78552d22dMarc Blank if (newMessage.mServerId == null || newMessage.mServerId.equals("") 11335c523858385176c33a7456bb84035de78552d22dMarc Blank || newMessage.mServerId.startsWith(LOCAL_SERVERID_PREFIX)) { 11345c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11355c523858385176c33a7456bb84035de78552d22dMarc Blank } 11365c523858385176c33a7456bb84035de78552d22dMarc Blank 11375c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. Escape early if we can't find the local mailbox 11385c523858385176c33a7456bb84035de78552d22dMarc Blank // TODO smaller projection here 11395c523858385176c33a7456bb84035de78552d22dMarc Blank Mailbox oldMailbox = getRemoteMailboxForMessage(context, oldMessage); 11405c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox == null) { 11415c523858385176c33a7456bb84035de78552d22dMarc Blank // can't find old mailbox, it may have been deleted. just return. 11425c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11435c523858385176c33a7456bb84035de78552d22dMarc Blank } 11445c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. We don't support delete-from-trash here 11455c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox.mType == Mailbox.TYPE_TRASH) { 11465c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11475c523858385176c33a7456bb84035de78552d22dMarc Blank } 11485c523858385176c33a7456bb84035de78552d22dMarc Blank 11495c523858385176c33a7456bb84035de78552d22dMarc Blank // The rest of this method handles server-side deletion 11505c523858385176c33a7456bb84035de78552d22dMarc Blank 11515c523858385176c33a7456bb84035de78552d22dMarc Blank // 4. Find the remote mailbox (that we deleted from), and open it 11525c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteFolder = remoteStore.getFolder(oldMailbox.mServerId); 11535c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteFolder.exists()) { 11545c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11555c523858385176c33a7456bb84035de78552d22dMarc Blank } 11565c523858385176c33a7456bb84035de78552d22dMarc Blank 11575c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 11585c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 11595c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11605c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11615c523858385176c33a7456bb84035de78552d22dMarc Blank } 11625c523858385176c33a7456bb84035de78552d22dMarc Blank 11635c523858385176c33a7456bb84035de78552d22dMarc Blank // 5. Find the remote original message 11645c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteFolder.getMessage(oldMessage.mServerId); 11655c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 11665c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11675c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11685c523858385176c33a7456bb84035de78552d22dMarc Blank } 11695c523858385176c33a7456bb84035de78552d22dMarc Blank 11705c523858385176c33a7456bb84035de78552d22dMarc Blank // 6. Find the remote trash folder, and create it if not found 11715c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteTrashFolder = remoteStore.getFolder(newMailbox.mServerId); 11725c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteTrashFolder.exists()) { 11735c523858385176c33a7456bb84035de78552d22dMarc Blank /* 11745c523858385176c33a7456bb84035de78552d22dMarc Blank * If the remote trash folder doesn't exist we try to create it. 11755c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11765c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.create(FolderType.HOLDS_MESSAGES); 11775c523858385176c33a7456bb84035de78552d22dMarc Blank } 11785c523858385176c33a7456bb84035de78552d22dMarc Blank 1179c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // 7. Try to copy the message into the remote trash folder 11805c523858385176c33a7456bb84035de78552d22dMarc Blank // Note, this entire section will be skipped for POP3 because there's no remote trash 11815c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.exists()) { 11825c523858385176c33a7456bb84035de78552d22dMarc Blank /* 11835c523858385176c33a7456bb84035de78552d22dMarc Blank * Because remoteTrashFolder may be new, we need to explicitly open it 11845c523858385176c33a7456bb84035de78552d22dMarc Blank */ 11855c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.open(OpenMode.READ_WRITE); 11865c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) { 11875c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 11885c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 11895c523858385176c33a7456bb84035de78552d22dMarc Blank return; 11905c523858385176c33a7456bb84035de78552d22dMarc Blank } 11915c523858385176c33a7456bb84035de78552d22dMarc Blank 11925c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.copyMessages(new Message[] { remoteMessage }, remoteTrashFolder, 11935c523858385176c33a7456bb84035de78552d22dMarc Blank new Folder.MessageUpdateCallbacks() { 11945c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 11955c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageUidChange(Message message, String newUid) { 11965c523858385176c33a7456bb84035de78552d22dMarc Blank // update the UID in the local trash folder, because some stores will 11975c523858385176c33a7456bb84035de78552d22dMarc Blank // have to change it when copying to remoteTrashFolder 11985c523858385176c33a7456bb84035de78552d22dMarc Blank ContentValues cv = new ContentValues(); 11995c523858385176c33a7456bb84035de78552d22dMarc Blank cv.put(EmailContent.Message.SERVER_ID, newUid); 12005c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().update(newMessage.getUri(), cv, null, null); 12015c523858385176c33a7456bb84035de78552d22dMarc Blank } 12025c523858385176c33a7456bb84035de78552d22dMarc Blank 12035c523858385176c33a7456bb84035de78552d22dMarc Blank /** 12045c523858385176c33a7456bb84035de78552d22dMarc Blank * This will be called if the deleted message doesn't exist and can't be 12055c523858385176c33a7456bb84035de78552d22dMarc Blank * deleted (e.g. it was already deleted from the server.) In this case, 12065c523858385176c33a7456bb84035de78552d22dMarc Blank * attempt to delete the local copy as well. 12075c523858385176c33a7456bb84035de78552d22dMarc Blank */ 12085c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 12095c523858385176c33a7456bb84035de78552d22dMarc Blank public void onMessageNotFound(Message message) { 12105c523858385176c33a7456bb84035de78552d22dMarc Blank context.getContentResolver().delete(newMessage.getUri(), null, null); 12115c523858385176c33a7456bb84035de78552d22dMarc Blank } 12125c523858385176c33a7456bb84035de78552d22dMarc Blank }); 12135c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12145c523858385176c33a7456bb84035de78552d22dMarc Blank } 12155c523858385176c33a7456bb84035de78552d22dMarc Blank 12165c523858385176c33a7456bb84035de78552d22dMarc Blank // 8. Delete the message from the remote source folder 12175c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 12185c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.expunge(); 12195c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.close(false); 12205c523858385176c33a7456bb84035de78552d22dMarc Blank } 12215c523858385176c33a7456bb84035de78552d22dMarc Blank 12225c523858385176c33a7456bb84035de78552d22dMarc Blank /** 12235c523858385176c33a7456bb84035de78552d22dMarc Blank * Process a pending trash message command. 12245c523858385176c33a7456bb84035de78552d22dMarc Blank * 12255c523858385176c33a7456bb84035de78552d22dMarc Blank * @param remoteStore the remote store we're working in 12265c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMailbox The local trash mailbox 12275c523858385176c33a7456bb84035de78552d22dMarc Blank * @param oldMessage The message that was deleted from the trash 12285c523858385176c33a7456bb84035de78552d22dMarc Blank */ 12291b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static void processPendingDeleteFromTrash(Store remoteStore, 12307d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler Mailbox oldMailbox, EmailContent.Message oldMessage) 12315c523858385176c33a7456bb84035de78552d22dMarc Blank throws MessagingException { 12325c523858385176c33a7456bb84035de78552d22dMarc Blank 12335c523858385176c33a7456bb84035de78552d22dMarc Blank // 1. We only support delete-from-trash here 12345c523858385176c33a7456bb84035de78552d22dMarc Blank if (oldMailbox.mType != Mailbox.TYPE_TRASH) { 12355c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12365c523858385176c33a7456bb84035de78552d22dMarc Blank } 12375c523858385176c33a7456bb84035de78552d22dMarc Blank 12385c523858385176c33a7456bb84035de78552d22dMarc Blank // 2. Find the remote trash folder (that we are deleting from), and open it 12395c523858385176c33a7456bb84035de78552d22dMarc Blank Folder remoteTrashFolder = remoteStore.getFolder(oldMailbox.mServerId); 12405c523858385176c33a7456bb84035de78552d22dMarc Blank if (!remoteTrashFolder.exists()) { 12415c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12425c523858385176c33a7456bb84035de78552d22dMarc Blank } 12435c523858385176c33a7456bb84035de78552d22dMarc Blank 12445c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.open(OpenMode.READ_WRITE); 12455c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteTrashFolder.getMode() != OpenMode.READ_WRITE) { 12465c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12475c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12485c523858385176c33a7456bb84035de78552d22dMarc Blank } 12495c523858385176c33a7456bb84035de78552d22dMarc Blank 12505c523858385176c33a7456bb84035de78552d22dMarc Blank // 3. Find the remote original message 12515c523858385176c33a7456bb84035de78552d22dMarc Blank Message remoteMessage = remoteTrashFolder.getMessage(oldMessage.mServerId); 12525c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteMessage == null) { 12535c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12545c523858385176c33a7456bb84035de78552d22dMarc Blank return; 12555c523858385176c33a7456bb84035de78552d22dMarc Blank } 12565c523858385176c33a7456bb84035de78552d22dMarc Blank 12575c523858385176c33a7456bb84035de78552d22dMarc Blank // 4. Delete the message from the remote trash folder 12585c523858385176c33a7456bb84035de78552d22dMarc Blank remoteMessage.setFlag(Flag.DELETED, true); 12595c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.expunge(); 12605c523858385176c33a7456bb84035de78552d22dMarc Blank remoteTrashFolder.close(false); 12615c523858385176c33a7456bb84035de78552d22dMarc Blank } 12625c523858385176c33a7456bb84035de78552d22dMarc Blank 12635c523858385176c33a7456bb84035de78552d22dMarc Blank /** 1264c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * Process a pending append message command. This command uploads a local message to the 1265c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * server, first checking to be sure that the server message is not newer than 1266c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * the local message. 1267c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * 1268c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param remoteStore the remote store we're working in 1269c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param mailbox The mailbox we're appending to 1270c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @param message The message we're appending 1271c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu * @return true if successfully uploaded 1272c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu */ 12737d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler private static boolean processPendingAppend(Context context, Store remoteStore, Mailbox mailbox, 12747d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler EmailContent.Message message) 1275c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu throws MessagingException { 1276c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean updateInternalDate = false; 1277c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean updateMessage = false; 1278c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu boolean deleteMessage = false; 1279c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1280c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 1. Find the remote folder that we're appending to and create and/or open it 1281c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 1282c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (!remoteFolder.exists()) { 1283c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { 1284c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // This is a (hopefully) transient error and we return false to try again later 1285c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return false; 1286c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1287c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1288c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.open(OpenMode.READ_WRITE); 1289c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteFolder.getMode() != OpenMode.READ_WRITE) { 1290c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return false; 1291c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1292c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1293c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 2. If possible, load a remote message with the matching UID 1294c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message remoteMessage = null; 1295c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (message.mServerId != null && message.mServerId.length() > 0) { 1296c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteMessage = remoteFolder.getMessage(message.mServerId); 1297c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1298c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1299c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3. If a remote message could not be found, upload our local message 1300c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteMessage == null) { 130137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: 130237b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // if we have a serverId and remoteMessage is still null, then probably the message 130337b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // has been deleted and we should delete locally. 1304c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3a. Create a legacy message to upload 1305c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message localMessage = LegacyConversions.makeMessage(context, message); 1306c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3b. Upload it 13077d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler //FetchProfile fp = new FetchProfile(); 13087d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler //fp.add(FetchProfile.Item.BODY); 130937b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // Note that this operation will assign the Uid to localMessage 1310c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.appendMessages(new Message[] { localMessage }); 1311c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1312c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 3b. And record the UID from the server 1313c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerId = localMessage.getUid(); 1314c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateInternalDate = true; 1315c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1316c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else { 1317c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4. If the remote message exists we need to determine which copy to keep. 131837b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: 131937b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // I don't see a good reason we should be here. If the message already has a serverId, 132037b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // then we should be handling it in processPendingUpdates(), 132137b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // not processPendingUploads() 1322c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu FetchProfile fp = new FetchProfile(); 1323c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.add(FetchProfile.Item.ENVELOPE); 1324c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); 1325c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Date localDate = new Date(message.mServerTimeStamp); 1326c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Date remoteDate = remoteMessage.getInternalDate(); 1327c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteDate != null && remoteDate.compareTo(localDate) > 0) { 1328c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4a. If the remote message is newer than ours we'll just 1329c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // delete ours and move on. A sync will get the server message 1330c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // if we need to be able to see it. 1331c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu deleteMessage = true; 1332c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else { 1333c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4b. Otherwise we'll upload our message and then delete the remote message. 1334c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1335c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // Create a legacy message to upload 133637b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // TODO: This strategy has a problem: This will create a second message, 133737b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // so that at least temporarily, we will have two messages for what the 133837b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // user would think of as one. 1339c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message localMessage = LegacyConversions.makeMessage(context, message); 1340c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1341c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4c. Upload it 1342c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.clear(); 1343c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp = new FetchProfile(); 1344c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp.add(FetchProfile.Item.BODY); 1345c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.appendMessages(new Message[] { localMessage }); 1346c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1347c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 4d. Record the UID and new internalDate from the server 1348c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerId = localMessage.getUid(); 1349c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateInternalDate = true; 1350c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1351c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 135237b539063d30e6b59cbbbdda470de81d41025e51Martin Hibdon // 4e. And delete the old copy of the message from the server. 1353c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteMessage.setFlag(Flag.DELETED, true); 1354c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1355c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1356c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1357c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 5. If requested, Best-effort to capture new "internaldate" from the server 1358c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (updateInternalDate && message.mServerId != null) { 1359c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu try { 1360c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Message remoteMessage2 = remoteFolder.getMessage(message.mServerId); 1361c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (remoteMessage2 != null) { 1362c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu FetchProfile fp2 = new FetchProfile(); 1363c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu fp2.add(FetchProfile.Item.ENVELOPE); 1364c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu remoteFolder.fetch(new Message[] { remoteMessage2 }, fp2, null); 1365c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu message.mServerTimeStamp = remoteMessage2.getInternalDate().getTime(); 1366c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu updateMessage = true; 1367c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1368c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } catch (MessagingException me) { 1369c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // skip it - we can live without this 1370c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1371c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1372c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1373c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu // 6. Perform required edits to local copy of message 1374c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (deleteMessage || updateMessage) { 1375c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, message.mId); 1376c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu ContentResolver resolver = context.getContentResolver(); 1377c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu if (deleteMessage) { 1378c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu resolver.delete(uri, null, null); 1379c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } else if (updateMessage) { 1380c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu ContentValues cv = new ContentValues(); 1381c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu cv.put(EmailContent.Message.SERVER_ID, message.mServerId); 1382c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu cv.put(EmailContent.Message.SERVER_TIMESTAMP, message.mServerTimeStamp); 1383c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu resolver.update(uri, cv, null, null); 1384c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1385c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1386c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1387c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu return true; 1388c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu } 1389c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu 1390c5c9c1c69e206b7632848a2535363693263f7fc9Yu Ping Hu /** 13915c523858385176c33a7456bb84035de78552d22dMarc Blank * A message and numeric uid that's easily sortable 13925c523858385176c33a7456bb84035de78552d22dMarc Blank */ 13935c523858385176c33a7456bb84035de78552d22dMarc Blank private static class SortableMessage { 13945c523858385176c33a7456bb84035de78552d22dMarc Blank private final Message mMessage; 13955c523858385176c33a7456bb84035de78552d22dMarc Blank private final long mUid; 13965c523858385176c33a7456bb84035de78552d22dMarc Blank 13975c523858385176c33a7456bb84035de78552d22dMarc Blank SortableMessage(Message message, long uid) { 13985c523858385176c33a7456bb84035de78552d22dMarc Blank mMessage = message; 13995c523858385176c33a7456bb84035de78552d22dMarc Blank mUid = uid; 14005c523858385176c33a7456bb84035de78552d22dMarc Blank } 14015c523858385176c33a7456bb84035de78552d22dMarc Blank } 14025c523858385176c33a7456bb84035de78552d22dMarc Blank 14031b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static int searchMailboxImpl(final Context context, final long accountId, 14041b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy final SearchParams searchParams, final long destMailboxId) throws MessagingException { 14055c523858385176c33a7456bb84035de78552d22dMarc Blank final Account account = Account.restoreAccountWithId(context, accountId); 14065c523858385176c33a7456bb84035de78552d22dMarc Blank final Mailbox mailbox = Mailbox.restoreMailboxWithId(context, searchParams.mMailboxId); 14075c523858385176c33a7456bb84035de78552d22dMarc Blank final Mailbox destMailbox = Mailbox.restoreMailboxWithId(context, destMailboxId); 14085c523858385176c33a7456bb84035de78552d22dMarc Blank if (account == null || mailbox == null || destMailbox == null) { 1409560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "Attempted search for " + searchParams 14105c523858385176c33a7456bb84035de78552d22dMarc Blank + " but account or mailbox information was missing"); 14115c523858385176c33a7456bb84035de78552d22dMarc Blank return 0; 14125c523858385176c33a7456bb84035de78552d22dMarc Blank } 14135c523858385176c33a7456bb84035de78552d22dMarc Blank 14145c523858385176c33a7456bb84035de78552d22dMarc Blank // Tell UI that we're loading messages 1415af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler final ContentValues statusValues = new ContentValues(2); 1416af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.LIVE_QUERY); 1417af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler destMailbox.update(context, statusValues); 14185c523858385176c33a7456bb84035de78552d22dMarc Blank 1419768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Store remoteStore = Store.getInstance(account, context); 1420768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Folder remoteFolder = remoteStore.getFolder(mailbox.mServerId); 14215c523858385176c33a7456bb84035de78552d22dMarc Blank remoteFolder.open(OpenMode.READ_WRITE); 14225c523858385176c33a7456bb84035de78552d22dMarc Blank 14235c523858385176c33a7456bb84035de78552d22dMarc Blank SortableMessage[] sortableMessages = new SortableMessage[0]; 14245c523858385176c33a7456bb84035de78552d22dMarc Blank if (searchParams.mOffset == 0) { 14255c523858385176c33a7456bb84035de78552d22dMarc Blank // Get the "bare" messages (basically uid) 1426768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final Message[] remoteMessages = remoteFolder.getMessages(searchParams, null); 1427768c6b86db854569a687f68c9b8f232e9ebf1573Tony Mantler final int remoteCount = remoteMessages.length; 14285c523858385176c33a7456bb84035de78552d22dMarc Blank if (remoteCount > 0) { 14295c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages = new SortableMessage[remoteCount]; 14305c523858385176c33a7456bb84035de78552d22dMarc Blank int i = 0; 14315c523858385176c33a7456bb84035de78552d22dMarc Blank for (Message msg : remoteMessages) { 14325c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages[i++] = new SortableMessage(msg, Long.parseLong(msg.getUid())); 14335c523858385176c33a7456bb84035de78552d22dMarc Blank } 14345c523858385176c33a7456bb84035de78552d22dMarc Blank // Sort the uid's, most recent first 14355c523858385176c33a7456bb84035de78552d22dMarc Blank // Note: Not all servers will be nice and return results in the order of request; 14365c523858385176c33a7456bb84035de78552d22dMarc Blank // those that do will see messages arrive from newest to oldest 14375c523858385176c33a7456bb84035de78552d22dMarc Blank Arrays.sort(sortableMessages, new Comparator<SortableMessage>() { 14385c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 14395c523858385176c33a7456bb84035de78552d22dMarc Blank public int compare(SortableMessage lhs, SortableMessage rhs) { 14405c523858385176c33a7456bb84035de78552d22dMarc Blank return lhs.mUid > rhs.mUid ? -1 : lhs.mUid < rhs.mUid ? 1 : 0; 14415c523858385176c33a7456bb84035de78552d22dMarc Blank } 14425c523858385176c33a7456bb84035de78552d22dMarc Blank }); 14435c523858385176c33a7456bb84035de78552d22dMarc Blank sSearchResults.put(accountId, sortableMessages); 14445c523858385176c33a7456bb84035de78552d22dMarc Blank } 14455c523858385176c33a7456bb84035de78552d22dMarc Blank } else { 14465c523858385176c33a7456bb84035de78552d22dMarc Blank sortableMessages = sSearchResults.get(accountId); 14475c523858385176c33a7456bb84035de78552d22dMarc Blank } 14485c523858385176c33a7456bb84035de78552d22dMarc Blank 14495c523858385176c33a7456bb84035de78552d22dMarc Blank final int numSearchResults = sortableMessages.length; 14505c523858385176c33a7456bb84035de78552d22dMarc Blank final int numToLoad = 1451c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon Math.min(numSearchResults - searchParams.mOffset, searchParams.mLimit); 14527d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler destMailbox.updateMessageCount(context, numSearchResults); 14535c523858385176c33a7456bb84035de78552d22dMarc Blank if (numToLoad <= 0) { 14545c523858385176c33a7456bb84035de78552d22dMarc Blank return 0; 14555c523858385176c33a7456bb84035de78552d22dMarc Blank } 14565c523858385176c33a7456bb84035de78552d22dMarc Blank 14575c523858385176c33a7456bb84035de78552d22dMarc Blank final ArrayList<Message> messageList = new ArrayList<Message>(); 14585c523858385176c33a7456bb84035de78552d22dMarc Blank for (int i = searchParams.mOffset; i < numToLoad + searchParams.mOffset; i++) { 14595c523858385176c33a7456bb84035de78552d22dMarc Blank messageList.add(sortableMessages[i].mMessage); 14605c523858385176c33a7456bb84035de78552d22dMarc Blank } 14615c523858385176c33a7456bb84035de78552d22dMarc Blank // Get everything in one pass, rather than two (as in sync); this starts getting us 14625c523858385176c33a7456bb84035de78552d22dMarc Blank // usable results quickly. 14635c523858385176c33a7456bb84035de78552d22dMarc Blank FetchProfile fp = new FetchProfile(); 14645c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.FLAGS); 14655c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.ENVELOPE); 14665c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.STRUCTURE); 14675c523858385176c33a7456bb84035de78552d22dMarc Blank fp.add(FetchProfile.Item.BODY_SANE); 14687d761f3de3b05657363a03c8afe3d4d1114a1b2fTony Mantler remoteFolder.fetch(messageList.toArray(new Message[messageList.size()]), fp, 14695c523858385176c33a7456bb84035de78552d22dMarc Blank new MessageRetrievalListener() { 14705c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 14715c523858385176c33a7456bb84035de78552d22dMarc Blank public void messageRetrieved(Message message) { 14725c523858385176c33a7456bb84035de78552d22dMarc Blank try { 14735c523858385176c33a7456bb84035de78552d22dMarc Blank // Determine if the new message was already known (e.g. partial) 1474c75f5880ab70d9f4938727587696b864bb4ea02aMartin Hibdon // And create or reload the full message info. 14755c523858385176c33a7456bb84035de78552d22dMarc Blank EmailContent.Message localMessage = new EmailContent.Message(); 14765c523858385176c33a7456bb84035de78552d22dMarc Blank try { 14775c523858385176c33a7456bb84035de78552d22dMarc Blank // Copy the fields that are available into the message 14785c523858385176c33a7456bb84035de78552d22dMarc Blank LegacyConversions.updateMessageFields(localMessage, 14795c523858385176c33a7456bb84035de78552d22dMarc Blank message, account.mId, mailbox.mId); 1480c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // Save off the mailbox that this message *really* belongs in. 1481c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // We need this information if we need to do more lookups 1482c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon // (like loading attachments) for this message. See b/11294681 1483c86fbb5bcbff72102f87747d94948f0749402229Martin Hibdon localMessage.mMainMailboxKey = localMessage.mMailboxKey; 14845c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage.mMailboxKey = destMailboxId; 14855c523858385176c33a7456bb84035de78552d22dMarc Blank // We load 50k or so; maybe it's complete, maybe not... 14865c523858385176c33a7456bb84035de78552d22dMarc Blank int flag = EmailContent.Message.FLAG_LOADED_COMPLETE; 14875c523858385176c33a7456bb84035de78552d22dMarc Blank // We store the serverId of the source mailbox into protocolSearchInfo 14885c523858385176c33a7456bb84035de78552d22dMarc Blank // This will be used by loadMessageForView, etc. to use the proper remote 14895c523858385176c33a7456bb84035de78552d22dMarc Blank // folder 14905c523858385176c33a7456bb84035de78552d22dMarc Blank localMessage.mProtocolSearchInfo = mailbox.mServerId; 14915c523858385176c33a7456bb84035de78552d22dMarc Blank if (message.getSize() > Store.FETCH_BODY_SANE_SUGGESTED_SIZE) { 14925c523858385176c33a7456bb84035de78552d22dMarc Blank flag = EmailContent.Message.FLAG_LOADED_PARTIAL; 14935c523858385176c33a7456bb84035de78552d22dMarc Blank } 14945c523858385176c33a7456bb84035de78552d22dMarc Blank Utilities.copyOneMessageToProvider(context, message, localMessage, flag); 14955c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (MessagingException me) { 1496560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 14975c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while copying downloaded message." + me); 14985c523858385176c33a7456bb84035de78552d22dMarc Blank } 14995c523858385176c33a7456bb84035de78552d22dMarc Blank } catch (Exception e) { 1500560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.e(Logging.LOG_TAG, 15015c523858385176c33a7456bb84035de78552d22dMarc Blank "Error while storing downloaded message." + e.toString()); 15025c523858385176c33a7456bb84035de78552d22dMarc Blank } 15035c523858385176c33a7456bb84035de78552d22dMarc Blank } 15045c523858385176c33a7456bb84035de78552d22dMarc Blank 15055c523858385176c33a7456bb84035de78552d22dMarc Blank @Override 15065c523858385176c33a7456bb84035de78552d22dMarc Blank public void loadAttachmentProgress(int progress) { 15075c523858385176c33a7456bb84035de78552d22dMarc Blank } 15085c523858385176c33a7456bb84035de78552d22dMarc Blank }); 1509af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler // Tell UI that we're done loading messages 1510af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.SYNC_TIME, System.currentTimeMillis()); 1511af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler statusValues.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC); 1512af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler destMailbox.update(context, statusValues); 1513af52f20930b2c0f24eeecc10be903712802d0965Tony Mantler 15145c523858385176c33a7456bb84035de78552d22dMarc Blank return numSearchResults; 15155c523858385176c33a7456bb84035de78552d22dMarc Blank } 15162075c97f608a853923980865b72147a5c8ef71f0Yu Ping Hu} 1517