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