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