EmailSyncAdapter.java revision b43c40606146babc767475bbabac5820efd4c604
1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
209b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentProviderOperation;
219b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentResolver;
229b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentUris;
239b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentValues;
249b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.OperationApplicationException;
259b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.database.Cursor;
269b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.os.RemoteException;
279b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.util.Log;
289b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.webkit.MimeTypeMap;
299b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank
30c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.internet.MimeMessage;
31c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.internet.MimeUtility;
32c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Address;
33c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MeetingInfo;
34c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MessagingException;
35c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.PackedString;
36c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Part;
37c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent;
38c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
39c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Body;
400565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
41c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
42c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns;
43c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns;
444d8774462ace9a45154b2df418b9f2fe7a9c685dBen Komaloimport com.android.emailcommon.provider.Mailbox;
45d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blankimport com.android.emailcommon.provider.Policy;
46e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.service.SyncWindow;
47e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.utility.AttachmentUtilities;
48e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.utility.ConversionUtilities;
49c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.utility.Utility;
5077186bb1a174432ef272584374942d8b9228e39cMarc Blankimport com.android.exchange.CommandStatusException;
5100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.Eas;
52498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport com.android.exchange.EasResponse;
5300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.EasSyncService;
544471a6960d352242cc65bddf7888cc5335840c74Marc Blankimport com.android.exchange.MessageMoveRequest;
550565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport com.android.exchange.R;
56c10a3beef4f048292e6a4ceb31527c5123801517Marc Blankimport com.android.exchange.utility.CalendarUtilities;
57277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blankimport com.google.common.annotations.VisibleForTesting;
58ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
590565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport org.apache.http.HttpStatus;
600565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport org.apache.http.entity.ByteArrayEntity;
610565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
624f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport java.io.ByteArrayInputStream;
6300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
6400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
6500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
6636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blankimport java.util.Calendar;
6700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.GregorianCalendar;
6800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.TimeZone;
69ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
70ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
71ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Sync adapter for EAS email
72ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
73ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
747c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic class EmailSyncAdapter extends AbstractSyncAdapter {
7500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
7691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_READ_COLUMN = 0;
7791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_MAILBOX_KEY_COLUMN = 1;
7891e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_SERVER_ID_COLUMN = 2;
7936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    private static final int UPDATES_FLAG_COLUMN = 3;
8091e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final String[] UPDATES_PROJECTION =
8136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        {MessageColumns.FLAG_READ, MessageColumns.MAILBOX_KEY, SyncColumns.SERVER_ID,
8236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            MessageColumns.FLAG_FAVORITE};
8349c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank
8449c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_ID_COLUMN = 0;
8549c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_SUBJECT_COLUMN = 1;
8618e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank    private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
8718e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank        new String[] { Message.RECORD_ID, MessageColumns.SUBJECT };
8818e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank
89c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    private static final String WHERE_BODY_SOURCE_MESSAGE_KEY = Body.SOURCE_MESSAGE_KEY + "=?";
90a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank    private static final String WHERE_MAILBOX_KEY_AND_MOVED =
91a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        MessageColumns.MAILBOX_KEY + "=? AND (" + MessageColumns.FLAGS + "&" +
92a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        EasSyncService.MESSAGE_FLAG_MOVED_MESSAGE + ")!=0";
934f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String[] FETCH_REQUEST_PROJECTION =
944f15001bdfd11c79524b4e44d60041967779e763Marc Blank        new String[] {EmailContent.RECORD_ID, SyncColumns.SERVER_ID};
954f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_RECORD_ID = 0;
964f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_SERVER_ID = 1;
974f15001bdfd11c79524b4e44d60041967779e763Marc Blank
984f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
9991e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
1009b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1019b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_REPLY = 1;
1029b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1039b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_REPLY_ALL = 2;
1049b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1059b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_FORWARD = 3;
106422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank
107498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final String[] mBindArguments = new String[2];
108498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final String[] mBindArgument = new String[1];
109ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
110936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
111936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
112936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
113936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
114498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final ArrayList<FetchRequest> mFetchRequestList = new ArrayList<FetchRequest>();
1154f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private boolean mFetchNeeded = false;
116ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1178efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    // Holds the parser's value for isLooping()
118936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private boolean mIsLooping = false;
1198efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
120d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank    // The policy (if any) for this adapter's Account
121d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank    private final Policy mPolicy;
122d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank
1236f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public EmailSyncAdapter(EasSyncService service) {
1246f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        super(service);
125d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank        // If we've got an account with a policy, cache it now
126d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank        if (mAccount.mPolicyKey != 0) {
127d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank            mPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
128d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank        } else {
129d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank            mPolicy = null;
130d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank        }
1316f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    }
1326f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank
1336f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    @Override
1346f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public void wipe() {
1356f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.CONTENT_URI,
1366f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1376f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.DELETED_CONTENT_URI,
1386f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1396f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.UPDATED_CONTENT_URI,
1406f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
141057faf66b737bbc7df2eaf77ee7a63827785e1b9Marc Blank        mService.clearRequests();
142a261805b03b853cce662b679da3e16120d521b7eMarc Blank        mFetchRequestList.clear();
143134346f5b886e6b53074238546653cdc76bbe868Marc Blank        // Delete attachments...
144c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank        AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, mAccount.mId, mMailbox.mId);
145ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
146ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1474f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private String getEmailFilter() {
1488bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        int syncLookback = mMailbox.mSyncLookback;
149b43c40606146babc767475bbabac5820efd4c604Marc Blank        if (syncLookback == SyncWindow.SYNC_WINDOW_UNKNOWN
150b43c40606146babc767475bbabac5820efd4c604Marc Blank                || mMailbox.mType == Mailbox.TYPE_INBOX) {
1518bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank            syncLookback = mAccount.mSyncLookback;
1528bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        }
1538bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        switch (syncLookback) {
1540565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            case SyncWindow.SYNC_WINDOW_AUTO:
1550565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                return Eas.FILTER_AUTO;
156e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_DAY:
1574f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_DAY;
158e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_3_DAYS:
1594f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_3_DAYS;
160e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_WEEK:
1614f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
162e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_2_WEEKS:
1634f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_2_WEEKS;
164e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_MONTH:
1654f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_MONTH;
166e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_ALL:
1674f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_ALL;
1684f15001bdfd11c79524b4e44d60041967779e763Marc Blank            default:
1694f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
1704f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1714f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1724f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1734f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /**
1744f15001bdfd11c79524b4e44d60041967779e763Marc Blank     * Holder for fetch request information (record id and server id)
1754f15001bdfd11c79524b4e44d60041967779e763Marc Blank     */
176936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private static class FetchRequest {
177936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @SuppressWarnings("unused")
1784f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final long messageId;
1794f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final String serverId;
1804f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1814f15001bdfd11c79524b4e44d60041967779e763Marc Blank        FetchRequest(long _messageId, String _serverId) {
1824f15001bdfd11c79524b4e44d60041967779e763Marc Blank            messageId = _messageId;
1834f15001bdfd11c79524b4e44d60041967779e763Marc Blank            serverId = _serverId;
1844f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1854f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1864f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1874f15001bdfd11c79524b4e44d60041967779e763Marc Blank    @Override
1884f15001bdfd11c79524b4e44d60041967779e763Marc Blank    public void sendSyncOptions(Double protocolVersion, Serializer s)
1894f15001bdfd11c79524b4e44d60041967779e763Marc Blank            throws IOException  {
1904f15001bdfd11c79524b4e44d60041967779e763Marc Blank        mFetchRequestList.clear();
1914f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // Find partially loaded messages; this should typically be a rare occurrence
1924f15001bdfd11c79524b4e44d60041967779e763Marc Blank        Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
1934f15001bdfd11c79524b4e44d60041967779e763Marc Blank                FETCH_REQUEST_PROJECTION,
1944f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.FLAG_LOADED + "=" + Message.FLAG_LOADED_PARTIAL + " AND " +
1954f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.MAILBOX_KEY + "=?",
1964f15001bdfd11c79524b4e44d60041967779e763Marc Blank                new String[] {Long.toString(mMailbox.mId)}, null);
1974f15001bdfd11c79524b4e44d60041967779e763Marc Blank        try {
1984f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Put all of these messages into a list; we'll need both id and server id
1994f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (c.moveToNext()) {
2004f15001bdfd11c79524b4e44d60041967779e763Marc Blank                mFetchRequestList.add(new FetchRequest(c.getLong(FETCH_REQUEST_RECORD_ID),
2014f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        c.getString(FETCH_REQUEST_SERVER_ID)));
2024f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
2034f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } finally {
2044f15001bdfd11c79524b4e44d60041967779e763Marc Blank            c.close();
2054f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2064f15001bdfd11c79524b4e44d60041967779e763Marc Blank
2074f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // The "empty" case is typical; we send a request for changes, and also specify a sync
2084f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // window, body preference type (HTML for EAS 12.0 and later; MIME for EAS 2.5), and
2094f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // truncation
2104f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If there are fetch requests, we only want the fetches (i.e. no changes from the server)
2114f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // so we turn MIME support off.  Note that we are always using EAS 2.5 if there are fetch
2124f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // requests
2134f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (mFetchRequestList.isEmpty()) {
2144f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.tag(Tags.SYNC_DELETES_AS_MOVES);
2154f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.tag(Tags.SYNC_GET_CHANGES);
2164f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_WINDOW_SIZE, EMAIL_WINDOW_SIZE);
2174f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
2184f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the lookback appropriately (EAS calls this a "filter")
2190d8fe734daad069d8652688d62a2753b0457d1eaMarc Blank            String filter = getEmailFilter();
2200d8fe734daad069d8652688d62a2753b0457d1eaMarc Blank            // We shouldn't get FILTER_AUTO here, but if we do, make it something legal...
2210d8fe734daad069d8652688d62a2753b0457d1eaMarc Blank            if (filter.equals(Eas.FILTER_AUTO)) {
2220d8fe734daad069d8652688d62a2753b0457d1eaMarc Blank                filter = Eas.FILTER_3_DAYS;
2230d8fe734daad069d8652688d62a2753b0457d1eaMarc Blank            }
224aead58d49204e28a78523c19bd86ad14a0599318Marc Blank            s.data(Tags.SYNC_FILTER_TYPE, filter);
2254f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the truncation amount for all classes
2264f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
2274f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE);
2284f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // HTML for email
2294f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
2304f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
2314f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.end();
2324f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } else {
2334f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Use MIME data for EAS 2.5
2344f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_MIME);
2354f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
2364f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
2374f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
2384f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } else {
2394f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
2404f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Ask for plain text, rather than MIME data.  This guarantees that we'll get a usable
2414f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // text body
2424f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_TEXT);
2434f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
2444f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
2454f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2464f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
2474f15001bdfd11c79524b4e44d60041967779e763Marc Blank
248ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
24977186bb1a174432ef272584374942d8b9228e39cMarc Blank    public boolean parse(InputStream is) throws IOException, CommandStatusException {
25048af7392c82262d17700e3fbdccf3a582809d449Marc Blank        EasEmailSyncParser p = new EasEmailSyncParser(is, this);
2514f15001bdfd11c79524b4e44d60041967779e763Marc Blank        mFetchNeeded = false;
2528efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        boolean res = p.parse();
2538efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        // Hold on to the parser's value for isLooping() to pass back to the service
2548efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        mIsLooping = p.isLooping();
2554f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If we've need a body fetch, or we've just finished one, return true in order to continue
2564f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (mFetchNeeded || !mFetchRequestList.isEmpty()) {
2574f15001bdfd11c79524b4e44d60041967779e763Marc Blank            return true;
2584f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2590565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
2600565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // Don't check for "auto" on the initial sync
2610565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        if (!("0".equals(mMailbox.mSyncKey))) {
2620565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // We've completed the first successful sync
2630565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            if (getEmailFilter().equals(Eas.FILTER_AUTO)) {
2640565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                getAutomaticLookback();
2650565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank             }
2660565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
2670565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
2688efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return res;
2698efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    }
2708efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
2710565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    private void getAutomaticLookback() throws IOException {
2720565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // If we're using an auto lookback, check how many items in the past week
2730565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // TODO Make the literal ints below constants once we twiddle them a bit
2740565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        int items = getEstimate(Eas.FILTER_1_WEEK);
2750565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        int lookback;
2760565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        if (items > 1050) {
2770565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // Over 150/day, just use one day (smallest)
2780565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_DAY;
2790565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 350 || (items == -1)) {
2800565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 50-150/day, use 3 days (150 to 450 messages synced)
2810565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_3_DAYS;
2820565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 150) {
2830565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 20-50/day, use 1 week (140 to 350 messages synced)
2840565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_WEEK;
2850565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 75) {
2860565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 10-25/day, use 1 week (140 to 350 messages synced)
2870565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_2_WEEKS;
2880565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items < 5) {
2890565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // If there are only a couple, see if it makes sense to get everything
2900565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            items = getEstimate(Eas.FILTER_ALL);
2910565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            if (items >= 0 && items < 100) {
2920565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                lookback = SyncWindow.SYNC_WINDOW_ALL;
2930565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            } else {
2940565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                lookback = SyncWindow.SYNC_WINDOW_1_MONTH;
2950565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
2960565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else {
2970565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_MONTH;
2980565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
2990565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // Store the new lookback and persist it
3000565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        mMailbox.mSyncLookback = lookback;
3010565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        ContentValues cv = new ContentValues();
3020565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        cv.put(MailboxColumns.SYNC_LOOKBACK, lookback);
3030565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        mContentResolver.update(ContentUris.withAppendedId(
3040565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                Mailbox.CONTENT_URI, mMailbox.mId), cv, null, null);
3050565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // STOPSHIP Temporary UI - Let the user know
3060565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        CharSequence[] windowEntries = mContext.getResources().getTextArray(
3070565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                R.array.account_settings_mail_window_entries);
3080565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        Utility.showToast(mContext, "Auto lookback: " + windowEntries[lookback]);
3090565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
3100565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
311936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private static class GetItemEstimateParser extends Parser {
3120565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        private static final String TAG = "GetItemEstimateParser";
3130565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        private int mEstimate = -1;
3140565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3150565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public GetItemEstimateParser(InputStream in) throws IOException {
3160565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            super(in);
3170565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3180565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
319936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @Override
3200565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public boolean parse() throws IOException {
3210565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // Loop here through the remaining xml
3220565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
3230565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_GET_ITEM_ESTIMATE) {
3240565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseGetItemEstimate();
3250565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3260565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3270565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3280565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3290565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            return true;
3300565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3310565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3320565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseGetItemEstimate() throws IOException {
3330565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_GET_ITEM_ESTIMATE) != END) {
3340565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_RESPONSE) {
3350565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseResponse();
3360565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3370565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3380565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3390565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3400565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3410565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3420565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseResponse() throws IOException {
3430565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_RESPONSE) != END) {
3440565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_STATUS) {
3450565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    Log.d(TAG, "GIE status: " + getValue());
3460565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_COLLECTION) {
3470565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseCollection();
3480565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3490565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3500565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3510565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3520565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3530565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3540565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseCollection() throws IOException {
3550565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_COLLECTION) != END) {
3560565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_CLASS) {
3570565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    Log.d(TAG, "GIE class: " + getValue());
3580565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_COLLECTION_ID) {
3590565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    Log.d(TAG, "GIE collectionId: " + getValue());
3600565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_ESTIMATE) {
3610565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    mEstimate = getValueInt();
3620565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    Log.d(TAG, "GIE estimate: " + mEstimate);
3630565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3640565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3650565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3660565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3670565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3680565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
3690565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3700565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    /**
3710565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * Return the estimated number of items to be synced in the current mailbox, based on the
3720565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * passed in filter argument
3730565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @param filter an EAS "window" filter
3740565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @return the estimated number of items to be synced, or -1 if unknown
3750565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @throws IOException
3760565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     */
3770565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    private int getEstimate(String filter) throws IOException {
3780565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        Serializer s = new Serializer();
379277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex10 = mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE;
380277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex03 = mService.mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE;
381277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex07 = !ex10 && !ex03;
3820565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3830565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        String className = getCollectionName();
3840565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        String syncKey = getSyncKey();
3850565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        userLog("gie, sending ", className, " syncKey: ", syncKey);
386277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank
3870565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        s.start(Tags.GIE_GET_ITEM_ESTIMATE).start(Tags.GIE_COLLECTIONS);
3880565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        s.start(Tags.GIE_COLLECTION);
389277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        if (ex07) {
390277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2007 likes collection id first
391277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
392277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_FILTER_TYPE, filter);
393277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
394277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        } else if (ex03) {
395277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2003 needs the "class" element
3960565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            s.data(Tags.GIE_CLASS, className);
397277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
398277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
399277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_FILTER_TYPE, filter);
400277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        } else {
401277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2010 requires the filter inside an OPTIONS container and sync key first
402277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
403277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
404277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, filter).end();
4050565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
406277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        s.end().end().end().done(); // GIE_COLLECTION, GIE_COLLECTIONS, GIE_GET_ITEM_ESTIMATE
407277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank
4080565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        EasResponse resp = mService.sendHttpClientPost("GetItemEstimate",
4090565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                new ByteArrayEntity(s.toByteArray()), EasSyncService.COMMAND_TIMEOUT);
4100565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        int code = resp.getStatus();
4110565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        if (code == HttpStatus.SC_OK) {
4120565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            if (!resp.isEmpty()) {
4130565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                InputStream is = resp.getInputStream();
4140565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                GetItemEstimateParser gieParser = new GetItemEstimateParser(is);
4150565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                gieParser.parse();
4160565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                // Return the estimated number of items
4170565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                return gieParser.mEstimate;
4180565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
4190565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
4200565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // If we can't get an estimate, indicate this...
4210565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        return -1;
4220565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
4230565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
4248efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    /**
4258efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     * Return the value of isLooping() as returned from the parser
4268efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     */
4278efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    @Override
4288efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    public boolean isLooping() {
4298efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return mIsLooping;
430ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
431147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
432aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    @Override
433aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public boolean isSyncable() {
434aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank        return true;
435aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    }
436aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
437368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    public class EasEmailSyncParser extends AbstractSyncParser {
438ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
439147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
440ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
441ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
442498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final String mMailboxIdAsString;
443ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
444498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Message> newEmails = new ArrayList<Message>();
445498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Message> fetchedEmails = new ArrayList<Message>();
446498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Long> deletedEmails = new ArrayList<Long>();
447498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
44848af7392c82262d17700e3fbdccf3a582809d449Marc Blank
44948af7392c82262d17700e3fbdccf3a582809d449Marc Blank        public EasEmailSyncParser(InputStream in, EmailSyncAdapter adapter) throws IOException {
45048af7392c82262d17700e3fbdccf3a582809d449Marc Blank            super(in, adapter);
451ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mMailboxIdAsString = Long.toString(mMailbox.mId);
452ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
453ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
45477186bb1a174432ef272584374942d8b9228e39cMarc Blank        public EasEmailSyncParser(Parser parser, EmailSyncAdapter adapter) throws IOException {
45526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            super(parser, adapter);
45626d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            mMailboxIdAsString = Long.toString(mMailbox.mId);
45726d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
45826d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
45926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        public void addData (Message msg, int endingTag) throws IOException {
460ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<Attachment> atts = new ArrayList<Attachment>();
4614f15001bdfd11c79524b4e44d60041967779e763Marc Blank            boolean truncated = false;
462ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
46326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            while (nextTag(endingTag) != END) {
464ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
4657c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATTACHMENTS:
4665ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up
467d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        attachmentsParser(atts, msg);
468ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4697c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_TO:
47067698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mTo = Address.pack(Address.parse(getValue()));
471ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4727c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FROM:
473705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        Address[] froms = Address.parse(getValue());
474705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        if (froms != null && froms.length > 0) {
4754f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mDisplayName = froms[0].toFriendly();
476ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
477705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        msg.mFrom = Address.pack(froms);
478ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_CC:
48067698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mCc = Address.pack(Address.parse(getValue()));
481ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4827c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_REPLY_TO:
48367698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mReplyTo = Address.pack(Address.parse(getValue()));
484ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4857c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DATE_RECEIVED:
4868e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                        msg.mTimeStamp = Utility.parseEmailDateTimeToMillis(getValue());
487ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4887c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_SUBJECT:
489ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mSubject = getValue();
490ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4917c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
492ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mFlagRead = getValueInt() == 1;
493ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4947c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_BODY:
49500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyParser(msg);
49600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
4977c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
498ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        msg.mFlagFavorite = flagParser();
499ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
5004f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_TRUNCATED:
5014f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        truncated = getValueInt() == 1;
5024f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
5034f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_DATA:
5044f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // We get MIME data for EAS 2.5.  First we parse it, then we take the
5054f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // html and/or plain text data and store it in the message
506068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        if (truncated) {
507068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // If the MIME data is truncated, don't bother parsing it, because
508068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // it will take time and throw an exception anyway when EOF is reached
509068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // In this case, we will load the body separately by tagging the message
510068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // "partially loaded".
5113fb34bbdabba3ef5bc5742aa012fd9245944165fMarc Blank                            // Get the data (and ignore it)
5123fb34bbdabba3ef5bc5742aa012fd9245944165fMarc Blank                            getValue();
5134f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            userLog("Partially loaded: ", msg.mServerId);
5144f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mFlagLoaded = Message.FLAG_LOADED_PARTIAL;
5154f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            mFetchNeeded = true;
516068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        } else {
517068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            mimeBodyParser(msg, getValue());
5184f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        }
5194f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
5207c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_BODY:
52167698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        String text = getValue();
52267698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mText = text;
523ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5247531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    case Tags.EMAIL_MESSAGE_CLASS:
5257531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        String messageClass = getValue();
5267531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
527888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
5287531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
529888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
5307531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        }
5317531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        break;
532c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_MEETING_REQUEST:
533c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        meetingRequestParser(msg);
534c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
53577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.RIGHTS_LICENSE:
53677186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
53777186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
53877186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.EMAIL2_CONVERSATION_ID:
53977186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.EMAIL2_CONVERSATION_INDEX:
54077186bb1a174432ef272584374942d8b9228e39cMarc Blank                        // Note that the value of these two tags is a byte array
54177186bb1a174432ef272584374942d8b9228e39cMarc Blank                        getValueBytes();
54277186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
543422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                    case Tags.EMAIL2_LAST_VERB_EXECUTED:
544422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        int val = getValueInt();
545422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        if (val == LAST_VERB_REPLY || val == LAST_VERB_REPLY_ALL) {
546422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            // We aren't required to distinguish between reply and reply all here
547422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            msg.mFlags |= Message.FLAG_REPLIED_TO;
548422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        } else if (val == LAST_VERB_FORWARD) {
549422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            msg.mFlags |= Message.FLAG_FORWARDED;
550422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        }
551422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        break;
552ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
553ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
554ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
555ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
556ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
557ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (atts.size() > 0) {
558ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mAttachments = atts;
559ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
560ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
56167698e240187c902bed123bf18d342ff25ec75c7Marc Blank
5625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        /**
5635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * Set up the meetingInfo field in the message with various pieces of information gleaned
5645c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * from MeetingRequest tags.  This information will be used later to generate an appropriate
5655c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * reply email if the user chooses to respond
5665c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @param msg the Message being built
5675c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @throws IOException
5685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         */
569c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void meetingRequestParser(Message msg) throws IOException {
5705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            PackedString.Builder packedString = new PackedString.Builder();
571c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_MEETING_REQUEST) != END) {
572c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
5735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_DTSTAMP:
5745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTAMP, getValue());
5755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
576c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_START_TIME:
5775c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTART, getValue());
5785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
5795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_END_TIME:
5805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTEND, getValue());
5815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
5825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_ORGANIZER:
5835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_ORGANIZER_EMAIL, getValue());
5845c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
5855c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_LOCATION:
5865c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_LOCATION, getValue());
5875c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
5885c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_GLOBAL_OBJID:
589b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                        packedString.put(MeetingInfo.MEETING_UID,
590b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                                CalendarUtilities.getUidFromGlobalObjId(getValue()));
591c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
592c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_CATEGORIES:
59377186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
594c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
595c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCES:
596c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        recurrencesParser();
597c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
59839fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                    case Tags.EMAIL_RESPONSE_REQUESTED:
59939fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        packedString.put(MeetingInfo.MEETING_RESPONSE_REQUESTED, getValue());
60039fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        break;
601c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
602c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
603c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
604c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
6055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            if (msg.mSubject != null) {
6065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                packedString.put(MeetingInfo.MEETING_TITLE, msg.mSubject);
6075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            }
6085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            msg.mMeetingInfo = packedString.toString();
609c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
610c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
611c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void recurrencesParser() throws IOException {
612c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_RECURRENCES) != END) {
613c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
614c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCE:
61577186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
616f69266d6671aa2c55fd04117a36f74dd17f73067Marc Blank                        break;
617c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
618c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
619c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
620c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
621c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
622c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
623d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank        /**
624d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * Parse a message from the server stream.
625d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * @return the parsed Message
626d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * @throws IOException
627d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         */
62877186bb1a174432ef272584374942d8b9228e39cMarc Blank        private Message addParser() throws IOException, CommandStatusException {
629ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Message msg = new Message();
630ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mAccountKey = mAccount.mId;
631ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mMailboxKey = mMailbox.mId;
632f02459a766ddb1727d191daa0aeb559c8f848668Andrew Stadler            msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
633247d8026d6be4609c4d7a8b7be8f2f5a0908e511Marc Blank            // Default to 1 (success) in case we don't get this tag
634247d8026d6be4609c4d7a8b7be8f2f5a0908e511Marc Blank            int status = 1;
635ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
6367c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_ADD) != END) {
637ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
6387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
639ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mServerId = getValue();
640ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
641d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    case Tags.SYNC_STATUS:
642d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        status = getValueInt();
643d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        break;
6447c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_APPLICATION_DATA:
64526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank                        addData(msg, tag);
646ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
647ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
648ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
649ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
650ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
651d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            // For sync, status 1 = success
652d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            if (status != 1) {
65377186bb1a174432ef272584374942d8b9228e39cMarc Blank                throw new CommandStatusException(status, msg.mServerId);
654d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            }
655d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            return msg;
656ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
657ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
658ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        // For now, we only care about the "active" state
659ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        private Boolean flagParser() throws IOException {
660ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean state = false;
6617c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_FLAG) != END) {
662ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                switch (tag) {
6637c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG_STATUS:
664ce17455fc5abf061e252d495288d0d56404b0b62Marc Blank                        state = getValueInt() == 2;
665ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
666ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    default:
667ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        skipTag();
668ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                }
669ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            }
670ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            return state;
671ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        }
672ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank
67300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void bodyParser(Message msg) throws IOException {
67400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String bodyType = Eas.BODY_PREFERENCE_TEXT;
67500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String body = "";
6767c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_BODY) != END) {
67700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                switch (tag) {
6787c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_TYPE:
67900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyType = getValue();
68000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
6817c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_DATA:
68200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        body = getValue();
68300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
68400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    default:
68500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        skipTag();
68600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
68700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
68800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            // We always ask for TEXT or HTML; there's no third option
68900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
69000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mHtml = body;
69100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
69200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mText = body;
69300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
69400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
69500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
6964f15001bdfd11c79524b4e44d60041967779e763Marc Blank        /**
697068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * Parses untruncated MIME data, saving away the text parts
6984f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param msg the message we're building
6994f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param mimeData the MIME data we've received from the server
700068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * @throws IOException
7014f15001bdfd11c79524b4e44d60041967779e763Marc Blank         */
702068e073ffcb68e06785929208d6a6761b29030f3Marc Blank        private void mimeBodyParser(Message msg, String mimeData) throws IOException {
7034f15001bdfd11c79524b4e44d60041967779e763Marc Blank            try {
7044f15001bdfd11c79524b4e44d60041967779e763Marc Blank                ByteArrayInputStream in = new ByteArrayInputStream(mimeData.getBytes());
7054f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // The constructor parses the message
706068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeMessage mimeMessage = new MimeMessage(in);
707068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // Now process body parts & attachments
708068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> viewables = new ArrayList<Part>();
709068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // We'll ignore the attachments, as we'll get them directly from EAS
710068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> attachments = new ArrayList<Part>();
711068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeUtility.collectParts(mimeMessage, viewables, attachments);
712068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                Body tempBody = new Body();
713068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // updateBodyFields fills in the content fields of the Body
714e951b589c5134a1154ec3743d79236dee54a6519Marc Blank                ConversionUtilities.updateBodyFields(tempBody, msg, viewables);
715068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // But we need them in the message itself for handling during commit()
716068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                msg.mHtml = tempBody.mHtmlContent;
717068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                msg.mText = tempBody.mTextContent;
7184f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } catch (MessagingException e) {
719068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // This would most likely indicate a broken stream
720068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                throw new IOException(e);
7214f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
7224f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
7234f15001bdfd11c79524b4e44d60041967779e763Marc Blank
7245ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        private void attachmentsParser(ArrayList<Attachment> atts, Message msg) throws IOException {
7255ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENTS) != END) {
7265ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                switch (tag) {
7275ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.EMAIL_ATTACHMENT:
7285ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENT:  // BASE_ATTACHMENT is used in EAS 12.0 and up
7295ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        attachmentParser(atts, msg);
7305ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        break;
7315ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    default:
7325ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        skipTag();
7335ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                }
7345ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            }
7355ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        }
7365ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank
73700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void attachmentParser(ArrayList<Attachment> atts, Message msg) throws IOException {
738ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String fileName = null;
739ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String length = null;
740d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            String location = null;
741ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7427c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENT) != END) {
743ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
7445ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    // We handle both EAS 2.5 and 12.0+ attachments here
7457c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DISPLAY_NAME:
7465ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_DISPLAY_NAME:
747ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        fileName = getValue();
748ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
7497c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_NAME:
7505ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_FILE_REFERENCE:
751d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        location = getValue();
752ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
7537c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_SIZE:
7545ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ESTIMATED_DATA_SIZE:
755ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        length = getValue();
756ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
757ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
758ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
759ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
760ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
761ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7625ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((fileName != null) && (length != null) && (location != null)) {
763ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Attachment att = new Attachment();
764ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mEncoding = "base64";
765ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mSize = Long.parseLong(length);
766ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mFileName = fileName;
767d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                att.mLocation = location;
76800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                att.mMimeType = getMimeTypeFromFileName(fileName);
769057faf66b737bbc7df2eaf77ee7a63827785e1b9Marc Blank                att.mAccountKey = mService.mAccount.mId;
770d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                // Check if this attachment can't be downloaded due to an account policy
771d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                if (mPolicy != null) {
772d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                    if (mPolicy.mDontAllowAttachments ||
773d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                            (mPolicy.mMaxAttachmentSize > 0 &&
774d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                                    (att.mSize > mPolicy.mMaxAttachmentSize))) {
775d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                        att.mFlags = Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD;
776d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                    }
777d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                }
778ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                atts.add(att);
779ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mFlagAttachment = true;
780ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
781ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
782ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
78300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /**
784936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * Returns an appropriate mimetype for the given file name's extension. If a mimetype
785936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * cannot be determined, {@code application/<<x>>} [where @{code <<x>> is the extension,
786936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * if it exists or {@code application/octet-stream}].
78700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * At the moment, this is somewhat lame, since many file types aren't recognized
78800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @param fileName the file name to ponder
78900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
79000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // Note: The MimeTypeMap method currently uses a very limited set of mime types
79100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // A bug has been filed against this issue.
79200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        public String getMimeTypeFromFileName(String fileName) {
79300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String mimeType;
79400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            int lastDot = fileName.lastIndexOf('.');
79500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String extension = null;
7965ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
79719fd685351de80c62c9bc7f0f05fe96983a8078dMarc Blank                extension = fileName.substring(lastDot + 1).toLowerCase();
79800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
79900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (extension == null) {
80000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                // A reasonable default for now.
80100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = "application/octet-stream";
80200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
80300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
80400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                if (mimeType == null) {
80500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    mimeType = "application/" + extension;
80600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
80700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
80800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            return mimeType;
80900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
81000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
811ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private Cursor getServerIdCursor(String serverId, String[] projection) {
812c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            mBindArguments[0] = serverId;
813c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            mBindArguments[1] = mMailboxIdAsString;
814ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return mContentResolver.query(Message.CONTENT_URI, projection,
815c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null);
816ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
817ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
818936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
819936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
820048d45641b88c87172074aa5f29b3de307bc3712Marc Blank            while (nextTag(entryTag) != END) {
821ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
8227c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
823ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String serverId = getValue();
824ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        // Find the message in this mailbox with the given serverId
82518e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                        Cursor c = getServerIdCursor(serverId, MESSAGE_ID_SUBJECT_PROJECTION);
826ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
827ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
82849c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                deletes.add(c.getLong(MESSAGE_ID_SUBJECT_ID_COLUMN));
82918e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                if (Eas.USER_LOG) {
83049c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                    userLog("Deleting ", serverId + ", "
83149c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                            + c.getString(MESSAGE_ID_SUBJECT_SUBJECT_COLUMN));
83218e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                }
833ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
834ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
835ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
836ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
837ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
838ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
839ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
840ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
841ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
842ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
843ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
844936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
845ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        class ServerChange {
8469b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final long id;
8479b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Boolean read;
8489b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Boolean flag;
8499b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Integer flags;
850ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8519b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            ServerChange(long _id, Boolean _read, Boolean _flag, Integer _flags) {
852ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                id = _id;
853ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                read = _read;
854ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                flag = _flag;
8559b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                flags = _flags;
856ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
857ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
858ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
859936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
860936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        void changeParser(ArrayList<ServerChange> changes) throws IOException {
861ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String serverId = null;
862ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldRead = false;
863ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldFlag = false;
8649b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            int flags = 0;
865ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            long id = 0;
8667c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_CHANGE) != END) {
867ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
8687c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
869ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        serverId = getValue();
870ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
871ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
872ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
8730a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                                userLog("Changing ", serverId);
874ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
875ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                                oldFlag = c.getInt(Message.LIST_FAVORITE_COLUMN) == 1;
8769b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                                flags = c.getInt(Message.LIST_FLAGS_COLUMN);
877ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                id = c.getLong(Message.LIST_ID_COLUMN);
878ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
879ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
880ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
881ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
882ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
883d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    case Tags.SYNC_APPLICATION_DATA:
8849b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        changeApplicationDataParser(changes, oldRead, oldFlag, flags, id);
885d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        break;
886d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    default:
887d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        skipTag();
888d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                }
889d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            }
890d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        }
891d82abe7213806e478338af4202b3622f34b5d6feMarc Blank
892d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        private void changeApplicationDataParser(ArrayList<ServerChange> changes, Boolean oldRead,
8939b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                Boolean oldFlag, int oldFlags, long id) throws IOException {
894d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean read = null;
895d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean flag = null;
8969b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            Integer flags = null;
897d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
898d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                switch (tag) {
8997c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
900ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        read = getValueInt() == 1;
901ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
9027c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
903ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        flag = flagParser();
904ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
9059b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    case Tags.EMAIL2_LAST_VERB_EXECUTED:
9069b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        int val = getValueInt();
9079b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        // Clear out the old replied/forward flags and add in the new flag
9089b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        flags = oldFlags & ~(Message.FLAG_REPLIED_TO | Message.FLAG_FORWARDED);
9099b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        if (val == LAST_VERB_REPLY || val == LAST_VERB_REPLY_ALL) {
9109b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            // We aren't required to distinguish between reply and reply all here
9119b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            flags |= Message.FLAG_REPLIED_TO;
9129b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        } else if (val == LAST_VERB_FORWARD) {
9139b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            flags |= Message.FLAG_FORWARDED;
9149b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        }
9159b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        break;
916ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
917ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
918ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
919ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
9209b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            // See if there are flag changes re: read, flag (favorite) or replied/forwarded
9215ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if (((read != null) && !oldRead.equals(read)) ||
9229b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    ((flag != null) && !oldFlag.equals(flag)) || (flags != null)) {
9239b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                changes.add(new ServerChange(id, read, flag, flags));
924ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
925ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
926ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
92700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /* (non-Javadoc)
92800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @see com.android.exchange.adapter.EasContentParser#commandsParser()
92900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
930147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        @Override
93177186bb1a174432ef272584374942d8b9228e39cMarc Blank        public void commandsParser() throws IOException, CommandStatusException {
9327c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_COMMANDS) != END) {
9337c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                if (tag == Tags.SYNC_ADD) {
934d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    newEmails.add(addParser());
9350a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
936048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                } else if (tag == Tags.SYNC_DELETE || tag == Tags.SYNC_SOFT_DELETE) {
937048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                    deleteParser(deletedEmails, tag);
9380a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
9397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                } else if (tag == Tags.SYNC_CHANGE) {
940ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    changeParser(changedEmails);
9410a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
942ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } else
943ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    skipTag();
944ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
94548af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
94648af7392c82262d17700e3fbdccf3a582809d449Marc Blank
94748af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
9484f15001bdfd11c79524b4e44d60041967779e763Marc Blank        public void responsesParser() throws IOException {
9494f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (nextTag(Tags.SYNC_RESPONSES) != END) {
9504f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (tag == Tags.SYNC_ADD || tag == Tags.SYNC_CHANGE || tag == Tags.SYNC_DELETE) {
9514f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    // We can ignore all of these
9524f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } else if (tag == Tags.SYNC_FETCH) {
953d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    try {
954d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        fetchedEmails.add(addParser());
95577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    } catch (CommandStatusException sse) {
956d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        if (sse.mStatus == 8) {
957d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // 8 = object not found; delete the message from EmailProvider
958d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // No other status should be seen in a fetch response, except, perhaps,
959d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // for some temporary server failure
96077186bb1a174432ef272584374942d8b9228e39cMarc Blank                            mBindArguments[0] = sse.mItemId;
961d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            mBindArguments[1] = mMailboxIdAsString;
962d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            mContentResolver.delete(Message.CONTENT_URI,
963d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                                    WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments);
964d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        }
965d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    }
9664f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
9674f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
96848af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
96948af7392c82262d17700e3fbdccf3a582809d449Marc Blank
97048af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
971a1e1f139046dfb28ea69c46d392f70764ad6f822Andrew Stadler        public void commit() {
972ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Use a batch operation to handle the changes
973ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // TODO New mail notifications?  Who looks for these?
974ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
9754f15001bdfd11c79524b4e44d60041967779e763Marc Blank
9764f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (Message msg: fetchedEmails) {
9774f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Find the original message's id (by serverId and mailbox)
9784f15001bdfd11c79524b4e44d60041967779e763Marc Blank                Cursor c = getServerIdCursor(msg.mServerId, EmailContent.ID_PROJECTION);
9794f15001bdfd11c79524b4e44d60041967779e763Marc Blank                String id = null;
9804f15001bdfd11c79524b4e44d60041967779e763Marc Blank                try {
9814f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    if (c.moveToFirst()) {
9824f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        id = c.getString(EmailContent.ID_PROJECTION_COLUMN);
9834f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    }
9844f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } finally {
9854f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    c.close();
9864f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
9874f15001bdfd11c79524b4e44d60041967779e763Marc Blank
9884f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // If we find one, we do two things atomically: 1) set the body text for the
9894f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // message, and 2) mark the message loaded (i.e. completely loaded)
9904f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (id != null) {
9914f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    userLog("Fetched body successfully for ", id);
9924f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    mBindArgument[0] = id;
9934f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Body.CONTENT_URI)
9944f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withSelection(Body.MESSAGE_KEY + "=?", mBindArgument)
9954f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withValue(Body.TEXT_CONTENT, msg.mText)
9964f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
9974f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Message.CONTENT_URI)
9984f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withSelection(EmailContent.RECORD_ID + "=?", mBindArgument)
999068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            .withValue(Message.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE)
10004f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
10014f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
10024f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
10034f15001bdfd11c79524b4e44d60041967779e763Marc Blank
100477424af660458104b732bdcb718874b17d0cab3aMarc Blank            for (Message msg: newEmails) {
100577424af660458104b732bdcb718874b17d0cab3aMarc Blank                msg.addSaveOps(ops);
1006ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
10074f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1008ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            for (Long id : deletedEmails) {
1009ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                ops.add(ContentProviderOperation.newDelete(
1010ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
1011e951b589c5134a1154ec3743d79236dee54a6519Marc Blank                AttachmentUtilities.deleteAllAttachmentFiles(mContext, mAccount.mId, id);
1012ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
10134f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1014ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (!changedEmails.isEmpty()) {
1015ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Server wins in a conflict...
1016ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                for (ServerChange change : changedEmails) {
1017ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                     ContentValues cv = new ContentValues();
1018ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.read != null) {
1019ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_READ, change.read);
1020ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
1021ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.flag != null) {
1022ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_FAVORITE, change.flag);
1023ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
10249b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    if (change.flags != null) {
10259b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        cv.put(MessageColumns.FLAGS, change.flags);
10269b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    }
1027ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    ops.add(ContentProviderOperation.newUpdate(
1028ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
1029ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .withValues(cv)
1030ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .build());
1031ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1032ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
10338d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
10348d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            // We only want to update the sync key here
10358d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            ContentValues mailboxValues = new ContentValues();
10368d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            mailboxValues.put(Mailbox.SYNC_KEY, mMailbox.mSyncKey);
1037ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ops.add(ContentProviderOperation.newUpdate(
10388d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                    ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
10398d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        .withValues(mailboxValues).build());
1040ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10418480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            addCleanupOps(ops);
1042147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
10431b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            // No commits if we're stopped
10441b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            synchronized (mService.getSynchronizer()) {
10451b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                if (mService.isStopped()) return;
10461b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                try {
1047c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank                    mContentResolver.applyBatch(EmailContent.AUTHORITY, ops);
10480a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
10491b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                } catch (RemoteException e) {
10501b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    // There is nothing to be done here; fail by returning null
10511b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                } catch (OperationApplicationException e) {
10521b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    // There is nothing to be done here; fail by returning null
10531b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                }
1054ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
105577424af660458104b732bdcb718874b17d0cab3aMarc Blank        }
1056ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1057ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1058ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
1059ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String getCollectionName() {
1060ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return "Email";
1061ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1062ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
10638480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    private void addCleanupOps(ArrayList<ContentProviderOperation> ops) {
10648480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // If we've sent local deletions, clear out the deleted table
10658480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mDeletedIdList) {
10668480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
10678480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
10688480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
10698480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // And same with the updates
10708480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mUpdatedIdList) {
10718480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
10728480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI, id)).build());
10738480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
1074a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // Delete any moved messages (since we've just synced the mailbox, and no longer need the
1075a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // placeholder message); this prevents duplicates from appearing in the mailbox.
1076a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        mBindArgument[0] = Long.toString(mMailbox.mId);
1077a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        ops.add(ContentProviderOperation.newDelete(Message.CONTENT_URI)
1078a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank                .withSelection(WHERE_MAILBOX_KEY_AND_MOVED, mBindArgument).build());
10798480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
10808480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
10818480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    @Override
108248af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public void cleanup() {
10838480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        if (!mDeletedIdList.isEmpty() || !mUpdatedIdList.isEmpty()) {
10848480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
10858480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            addCleanupOps(ops);
10868480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            try {
108748af7392c82262d17700e3fbdccf3a582809d449Marc Blank                mContext.getContentResolver()
1088c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank                    .applyBatch(EmailContent.AUTHORITY, ops);
10898480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            } catch (RemoteException e) {
10908480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                // There is nothing to be done here; fail by returning null
10918480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            } catch (OperationApplicationException e) {
10928480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                // There is nothing to be done here; fail by returning null
10938480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            }
10948480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
10958480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
10968480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
109736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    private String formatTwo(int num) {
109836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        if (num < 10) {
109936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return "0" + (char)('0' + num);
110036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        } else
110136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return Integer.toString(num);
110236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
110336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
110436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    /**
110536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * Create date/time in RFC8601 format.  Oddly enough, for calendar date/time, Microsoft uses
110636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * a different format that excludes the punctuation (this is why I'm not putting this in a
110736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * parent class)
110836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     */
110936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public String formatDateTime(Calendar calendar) {
111036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        StringBuilder sb = new StringBuilder();
111136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        //YYYY-MM-DDTHH:MM:SS.MSSZ
111236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(calendar.get(Calendar.YEAR));
111336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
111436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MONTH) + 1));
111536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
111636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.DAY_OF_MONTH)));
111736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('T');
111836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.HOUR_OF_DAY)));
111936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
112036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MINUTE)));
112136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
112236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.SECOND)));
112336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(".000Z");
112436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        return sb.toString();
112536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
112636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
1127c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /**
1128c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Note that messages in the deleted database preserve the message's unique id; therefore, we
1129c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * can utilize this id to find references to the message.  The only reference situation at this
1130c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * point is in the Body table; it is when sending messages via SmartForward and SmartReply
1131c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
1132c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    private boolean messageReferenced(ContentResolver cr, long id) {
1133c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        mBindArgument[0] = Long.toString(id);
1134c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // See if this id is referenced in a body
1135c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Body.CONTENT_URI, Body.ID_PROJECTION, WHERE_BODY_SOURCE_MESSAGE_KEY,
1136c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                mBindArgument, null);
1137c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        try {
1138c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return c.moveToFirst();
1139c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        } finally {
1140c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            c.close();
11415acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank        }
1142c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
1143c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1144c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /*private*/ /**
1145c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Serialize commands to delete items from the server; as we find items to delete, add their
1146c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * id's to the deletedId's array
1147c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     *
1148c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param s the Serializer we're using to create post data
1149c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param deletedIds ids whose deletions are being sent to the server
1150c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param first whether or not this is the first command being sent
1151c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @return true if SYNC_COMMANDS hasn't been sent (false otherwise)
1152c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @throws IOException
1153c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
1154936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
1155c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    boolean sendDeletedItems(Serializer s, ArrayList<Long> deletedIds, boolean first)
1156c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            throws IOException {
1157c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
11585acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank
1159ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Find any of our deleted items
1160ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
1161ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
1162ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // We keep track of the list of deleted item id's so that we can remove them from the
1163ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // deleted table after the server receives our command
1164c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        deletedIds.clear();
1165ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1166ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
11675acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN);
11685acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                // Keep going if there's no serverId
11695acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                if (serverId == null) {
11705acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    continue;
1171c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                // Also check if this message is referenced elsewhere
1172c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                } else if (messageReferenced(cr, c.getLong(Message.CONTENT_ID_COLUMN))) {
1173c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    userLog("Postponing deletion of referenced message: ", serverId);
1174c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    continue;
11755acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                } else if (first) {
11767c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_COMMANDS);
1177ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    first = false;
1178ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1179ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Send the command to delete this message
11805acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
1181c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                deletedIds.add(c.getLong(Message.LIST_ID_COLUMN));
1182ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
1184ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
1185ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1186ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1187c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank       return first;
1188c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
1189c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1190c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    @Override
1191c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    public boolean sendLocalChanges(Serializer s) throws IOException {
1192c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
1193c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1194a261805b03b853cce662b679da3e16120d521b7eMarc Blank        if (getSyncKey().equals("0")) {
1195a261805b03b853cce662b679da3e16120d521b7eMarc Blank            return false;
1196a261805b03b853cce662b679da3e16120d521b7eMarc Blank        }
1197a261805b03b853cce662b679da3e16120d521b7eMarc Blank
1198c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // Never upsync from these folders
1199c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) {
1200c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return false;
1201c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        }
1202c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1203c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // This code is split out for unit testing purposes
1204c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true);
1205c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
12064f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (!mFetchRequestList.isEmpty()) {
12074f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Add FETCH commands for messages that need a body (i.e. we didn't find it during
12084f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // our earlier sync; this happens only in EAS 2.5 where the body couldn't be found
12094f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // after parsing the message's MIME data)
12104f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (firstCommand) {
12114f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_COMMANDS);
12124f15001bdfd11c79524b4e44d60041967779e763Marc Blank                firstCommand = false;
12134f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
12144f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (FetchRequest req: mFetchRequestList) {
12154f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_FETCH).data(Tags.SYNC_SERVER_ID, req.serverId).end();
12164f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
12174f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
12184f15001bdfd11c79524b4e44d60041967779e763Marc Blank
121991e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // Find our trash mailbox, since deletions will have been moved there...
122091e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        long trashMailboxId =
12210a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
122291e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
1223ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do the same now for updated items
1224c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
1225ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
122691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
122791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // We keep track of the list of updated item id's as we did above with deleted items
1228ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUpdatedIdList.clear();
1229ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1230ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
1231ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                long id = c.getLong(Message.LIST_ID_COLUMN);
1232ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Say we've handled this update
1233ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mUpdatedIdList.add(id);
1234ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We have the id of the changed item.  But first, we have to find out its current
1235ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // state, since the updated table saves the opriginal state
1236ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
1237ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        UPDATES_PROJECTION, null, null, null);
1238ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
1239ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // If this item no longer exists (shouldn't be possible), just move along
1240ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (!currentCursor.moveToFirst()) {
1241ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                         continue;
1242ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
12435acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    // Keep going if there's no serverId
12445acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    String serverId = currentCursor.getString(UPDATES_SERVER_ID_COLUMN);
12455acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    if (serverId == null) {
12465acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                        continue;
12475acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    }
124891e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    // If the message is now in the trash folder, it has been deleted by the user
124991e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) {
1250c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                         if (firstCommand) {
12517c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            s.start(Tags.SYNC_COMMANDS);
1252c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                            firstCommand = false;
125391e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        }
125491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        // Send the command to delete this message
12555acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                        s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
125691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        continue;
125791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    }
125891e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
125936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean flagChange = false;
126036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean readChange = false;
126136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
12624471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    long mailbox = currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN);
12634471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    if (mailbox != c.getLong(Message.LIST_MAILBOX_KEY_COLUMN)) {
12644471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // The message has moved to another mailbox; add a request for this
12654471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Note: The Sync command doesn't handle moving messages, so we need
12664471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // to handle this as a "request" (similar to meeting response and
12674471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // attachment load)
12684471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mService.addRequest(new MessageMoveRequest(id, mailbox));
12694471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Regardless of other changes that might be made, we don't want to indicate
12704471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // that this message has been updated until the move request has been
12714471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // handled (without this, a crash between the flag upsync and the move
12724471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // would cause the move to be lost)
12734471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mUpdatedIdList.remove(id);
12744471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    }
127536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
127636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // We can only send flag changes to the server in 12.0 or later
12774471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    int flag = 0;
1278d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    if (mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
127936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        flag = currentCursor.getInt(UPDATES_FLAG_COLUMN);
128036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != c.getInt(Message.LIST_FAVORITE_COLUMN)) {
128136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            flagChange = true;
128236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
128336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
128436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
128591e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    int read = currentCursor.getInt(UPDATES_READ_COLUMN);
128676eb7b252fc410f5f5d4e90ad54d4bde837de0aaMarc Blank                    if (read != c.getInt(Message.LIST_READ_COLUMN)) {
128736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        readChange = true;
128836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
128936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
129036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (!flagChange && !readChange) {
129136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        // In this case, we've got nothing to send to the server
1292ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        continue;
1293ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
129436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
1295c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    if (firstCommand) {
12967c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.SYNC_COMMANDS);
1297c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                        firstCommand = false;
1298ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
129936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // Send the change to "read" and "favorite" (flagged)
13007c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_CHANGE)
13017c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        .data(Tags.SYNC_SERVER_ID, c.getString(Message.LIST_SERVER_ID_COLUMN))
130236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        .start(Tags.SYNC_APPLICATION_DATA);
130336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (readChange) {
130436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        s.data(Tags.EMAIL_READ, Integer.toString(read));
130536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
130636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // "Flag" is a relatively complex concept in EAS 12.0 and above.  It is not only
130736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // the boolean "favorite" that we think of in Gmail, but it also represents a
130836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // follow up action, which can include a subject, start and due dates, and even
130936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // recurrences.  We don't support any of this as yet, but EAS 12.0 and higher
131036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // require that a flag contain a status, a type, and four date fields, two each
131136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // for start date and end (due) date.
131236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (flagChange) {
131336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != 0) {
131436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Status 2 = set flag
131536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.start(Tags.EMAIL_FLAG).data(Tags.EMAIL_FLAG_STATUS, "2");
131636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // "FollowUp" is the standard type
131736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.EMAIL_FLAG_TYPE, "FollowUp");
131836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            long now = System.currentTimeMillis();
131936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            Calendar calendar =
132036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                                GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
132136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now);
132236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Flags are required to have a start date and end date (duplicated)
132336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // First, we'll set the current date/time in GMT as the start time
132436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            String utc = formatDateTime(calendar);
132536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_START_DATE, utc).data(Tags.TASK_UTC_START_DATE, utc);
132636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // And then we'll use one week from today for completion date
132736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now + 1*WEEKS);
132836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            utc = formatDateTime(calendar);
132936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_DUE_DATE, utc).data(Tags.TASK_UTC_DUE_DATE, utc);
133036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.end();
133136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        } else {
133236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.tag(Tags.EMAIL_FLAG);
133336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
133436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
133536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    s.end().end(); // SYNC_APPLICATION_DATA, SYNC_CHANGE
1336ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
1337ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    currentCursor.close();
1338ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1339ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1340ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
1341ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
1342ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1343ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1344c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (!firstCommand) {
13457c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end(); // SYNC_COMMANDS
1346ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1347ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return false;
1348ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1349ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1350