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
18c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu// TODO: Deprecated, remove this file.
19c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
20ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
21ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
229b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentProviderOperation;
239b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentResolver;
249b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentUris;
259b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.ContentValues;
26c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Huimport android.content.Context;
279b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.content.OperationApplicationException;
289b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.database.Cursor;
290dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blankimport android.net.Uri;
309b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.os.RemoteException;
31527b5952ef438e010ed5a203f12b58a8a2909338Marc Blankimport android.os.TransactionTooLargeException;
32e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport android.provider.CalendarContract.Events;
33e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport android.text.Html;
34e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport android.text.SpannedString;
35ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blankimport android.text.TextUtils;
36b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugoimport android.util.Base64;
379b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blankimport android.webkit.MimeTypeMap;
389b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank
39c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.internet.MimeMessage;
40c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.internet.MimeUtility;
41c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Address;
42c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MeetingInfo;
43c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.MessagingException;
44c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.PackedString;
45c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.mail.Part;
460dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blankimport com.android.emailcommon.provider.Account;
47c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent;
480dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns;
49c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
50c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Body;
510565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
52c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
53c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns;
54c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.provider.EmailContent.SyncColumns;
554d8774462ace9a45154b2df418b9f2fe7a9c685dBen Komaloimport com.android.emailcommon.provider.Mailbox;
56d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blankimport com.android.emailcommon.provider.Policy;
5734d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blankimport com.android.emailcommon.provider.ProviderUnavailableException;
58e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.service.SyncWindow;
59e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.utility.AttachmentUtilities;
60e951b589c5134a1154ec3743d79236dee54a6519Marc Blankimport com.android.emailcommon.utility.ConversionUtilities;
61e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blankimport com.android.emailcommon.utility.TextUtilities;
62c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blankimport com.android.emailcommon.utility.Utility;
6377186bb1a174432ef272584374942d8b9228e39cMarc Blankimport com.android.exchange.CommandStatusException;
6400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.Eas;
65498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport com.android.exchange.EasResponse;
6600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.EasSyncService;
674471a6960d352242cc65bddf7888cc5335840c74Marc Blankimport com.android.exchange.MessageMoveRequest;
680565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport com.android.exchange.R;
69c10a3beef4f048292e6a4ceb31527c5123801517Marc Blankimport com.android.exchange.utility.CalendarUtilities;
70942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedyimport com.android.mail.utils.LogUtils;
71277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blankimport com.google.common.annotations.VisibleForTesting;
72ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
730565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport org.apache.http.HttpStatus;
740565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blankimport org.apache.http.entity.ByteArrayEntity;
750565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
764f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport java.io.ByteArrayInputStream;
7700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
7800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
7900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
8036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blankimport java.util.Calendar;
8100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.GregorianCalendar;
8200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.TimeZone;
83ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
84ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
85ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Sync adapter for EAS email
86ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
87ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
887c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic class EmailSyncAdapter extends AbstractSyncAdapter {
8900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
90110837ebff288a75f9bda067c38e2c46797d99b5Alon Albert    private static final String TAG = Eas.LOG_TAG;
9169c1a51ac81fe2c649e5f2069dc75565d868db98Ben Komalo
9291e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_READ_COLUMN = 0;
9391e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_MAILBOX_KEY_COLUMN = 1;
9491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_SERVER_ID_COLUMN = 2;
9536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    private static final int UPDATES_FLAG_COLUMN = 3;
9691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final String[] UPDATES_PROJECTION =
9736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        {MessageColumns.FLAG_READ, MessageColumns.MAILBOX_KEY, SyncColumns.SERVER_ID,
9836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            MessageColumns.FLAG_FAVORITE};
9949c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank
10049c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_ID_COLUMN = 0;
10149c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_SUBJECT_COLUMN = 1;
10218e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank    private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
10318e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank        new String[] { Message.RECORD_ID, MessageColumns.SUBJECT };
10418e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank
105c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    private static final String WHERE_BODY_SOURCE_MESSAGE_KEY = Body.SOURCE_MESSAGE_KEY + "=?";
106a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank    private static final String WHERE_MAILBOX_KEY_AND_MOVED =
107a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        MessageColumns.MAILBOX_KEY + "=? AND (" + MessageColumns.FLAGS + "&" +
108a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        EasSyncService.MESSAGE_FLAG_MOVED_MESSAGE + ")!=0";
1094f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String[] FETCH_REQUEST_PROJECTION =
1104f15001bdfd11c79524b4e44d60041967779e763Marc Blank        new String[] {EmailContent.RECORD_ID, SyncColumns.SERVER_ID};
1114f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_RECORD_ID = 0;
1124f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_SERVER_ID = 1;
1134f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1144f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
11591e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
1169b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1179b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_REPLY = 1;
1189b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1199b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_REPLY_ALL = 2;
1209b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    @VisibleForTesting
1219b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank    static final int LAST_VERB_FORWARD = 3;
122422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank
123936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
124936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
125936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
126936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
127498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final ArrayList<FetchRequest> mFetchRequestList = new ArrayList<FetchRequest>();
128ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1298efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    // Holds the parser's value for isLooping()
130936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private boolean mIsLooping = false;
1318efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
1326f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public EmailSyncAdapter(EasSyncService service) {
1336f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        super(service);
1346f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    }
1356f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank
1366f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    @Override
1376f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public void wipe() {
1386f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.CONTENT_URI,
1396f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1406f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.DELETED_CONTENT_URI,
1416f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1426f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.UPDATED_CONTENT_URI,
1436f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
144057faf66b737bbc7df2eaf77ee7a63827785e1b9Marc Blank        mService.clearRequests();
145a261805b03b853cce662b679da3e16120d521b7eMarc Blank        mFetchRequestList.clear();
146134346f5b886e6b53074238546653cdc76bbe868Marc Blank        // Delete attachments...
147c8e4352ea6cfa67f15140512e84af8ccede222d2Marc Blank        AttachmentUtilities.deleteAllMailboxAttachmentFiles(mContext, mAccount.mId, mMailbox.mId);
148ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
149ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1504f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private String getEmailFilter() {
1518bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        int syncLookback = mMailbox.mSyncLookback;
15235b892c7570b06e72c97c0915d0e41abc83ed6b0Yu Ping Hu        if (syncLookback == SyncWindow.SYNC_WINDOW_ACCOUNT
153b43c40606146babc767475bbabac5820efd4c604Marc Blank                || mMailbox.mType == Mailbox.TYPE_INBOX) {
1548bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank            syncLookback = mAccount.mSyncLookback;
1558bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        }
1568bad9caf330ff72a73d9b7b98ecba7ce5f57ffc9Marc Blank        switch (syncLookback) {
157e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_DAY:
1584f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_DAY;
159e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_3_DAYS:
1604f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_3_DAYS;
161e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_WEEK:
1624f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
163e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_2_WEEKS:
1644f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_2_WEEKS;
165e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_1_MONTH:
1664f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_MONTH;
167e951b589c5134a1154ec3743d79236dee54a6519Marc Blank            case SyncWindow.SYNC_WINDOW_ALL:
1684f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_ALL;
1694f15001bdfd11c79524b4e44d60041967779e763Marc Blank            default:
1709b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu                // Auto window is deprecated and will also use the default.
1714f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
1724f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1734f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1744f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1754f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /**
1764f15001bdfd11c79524b4e44d60041967779e763Marc Blank     * Holder for fetch request information (record id and server id)
1774f15001bdfd11c79524b4e44d60041967779e763Marc Blank     */
178936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private static class FetchRequest {
179936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @SuppressWarnings("unused")
1804f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final long messageId;
1814f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final String serverId;
1824f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1834f15001bdfd11c79524b4e44d60041967779e763Marc Blank        FetchRequest(long _messageId, String _serverId) {
1844f15001bdfd11c79524b4e44d60041967779e763Marc Blank            messageId = _messageId;
1854f15001bdfd11c79524b4e44d60041967779e763Marc Blank            serverId = _serverId;
1864f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1874f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1884f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1894f15001bdfd11c79524b4e44d60041967779e763Marc Blank    @Override
190e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank    public void sendSyncOptions(Double protocolVersion, Serializer s, boolean initialSync)
1914f15001bdfd11c79524b4e44d60041967779e763Marc Blank            throws IOException  {
192e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        if (initialSync) return;
1934f15001bdfd11c79524b4e44d60041967779e763Marc Blank        mFetchRequestList.clear();
1944f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // Find partially loaded messages; this should typically be a rare occurrence
1954f15001bdfd11c79524b4e44d60041967779e763Marc Blank        Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
1964f15001bdfd11c79524b4e44d60041967779e763Marc Blank                FETCH_REQUEST_PROJECTION,
1974f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.FLAG_LOADED + "=" + Message.FLAG_LOADED_PARTIAL + " AND " +
1984f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.MAILBOX_KEY + "=?",
1994f15001bdfd11c79524b4e44d60041967779e763Marc Blank                new String[] {Long.toString(mMailbox.mId)}, null);
2004f15001bdfd11c79524b4e44d60041967779e763Marc Blank        try {
2014f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Put all of these messages into a list; we'll need both id and server id
2024f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (c.moveToNext()) {
2034f15001bdfd11c79524b4e44d60041967779e763Marc Blank                mFetchRequestList.add(new FetchRequest(c.getLong(FETCH_REQUEST_RECORD_ID),
2044f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        c.getString(FETCH_REQUEST_SERVER_ID)));
2054f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
2064f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } finally {
2074f15001bdfd11c79524b4e44d60041967779e763Marc Blank            c.close();
2084f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2094f15001bdfd11c79524b4e44d60041967779e763Marc Blank
2104f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // The "empty" case is typical; we send a request for changes, and also specify a sync
2114f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // window, body preference type (HTML for EAS 12.0 and later; MIME for EAS 2.5), and
2124f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // truncation
2134f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If there are fetch requests, we only want the fetches (i.e. no changes from the server)
2144f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // so we turn MIME support off.  Note that we are always using EAS 2.5 if there are fetch
2154f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // requests
2164f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (mFetchRequestList.isEmpty()) {
2171d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            // Permanently delete if in trash mailbox
2181d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            // In Exchange 2003, deletes-as-moves tag = true; no tag = false
2191d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            // In Exchange 2007 and up, deletes-as-moves tag is "0" (false) or "1" (true)
2201d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            boolean isTrashMailbox = mMailbox.mType == Mailbox.TYPE_TRASH;
2211d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            if (protocolVersion < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
2221d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank                if (!isTrashMailbox) {
2231d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank                    s.tag(Tags.SYNC_DELETES_AS_MOVES);
2241d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank                }
2251d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            } else {
2261d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank                s.data(Tags.SYNC_DELETES_AS_MOVES, isTrashMailbox ? "0" : "1");
2271d4f429f583dd6ba18d0894b2b1d02afb968c96fMarc Blank            }
2284f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.tag(Tags.SYNC_GET_CHANGES);
2294f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_WINDOW_SIZE, EMAIL_WINDOW_SIZE);
2304f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
2314f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the lookback appropriately (EAS calls this a "filter")
2329b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu            s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
2334f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the truncation amount for all classes
2344f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
2354f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE);
2364f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // HTML for email
2374f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
2384f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
2394f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.end();
2404f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } else {
2414f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Use MIME data for EAS 2.5
2424f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_MIME);
2434f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
2444f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
2454f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
2464f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } else {
2474f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
2484f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Ask for plain text, rather than MIME data.  This guarantees that we'll get a usable
2494f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // text body
2504f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_TEXT);
2514f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
2524f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
2534f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2544f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
2554f15001bdfd11c79524b4e44d60041967779e763Marc Blank
256ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
25777186bb1a174432ef272584374942d8b9228e39cMarc Blank    public boolean parse(InputStream is) throws IOException, CommandStatusException {
25848af7392c82262d17700e3fbdccf3a582809d449Marc Blank        EasEmailSyncParser p = new EasEmailSyncParser(is, this);
2598efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        boolean res = p.parse();
2608efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        // Hold on to the parser's value for isLooping() to pass back to the service
2618efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        mIsLooping = p.isLooping();
2624f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If we've need a body fetch, or we've just finished one, return true in order to continue
263c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        if (p.fetchNeeded() || !mFetchRequestList.isEmpty()) {
2644f15001bdfd11c79524b4e44d60041967779e763Marc Blank            return true;
2654f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2660565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
2678efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return res;
2688efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    }
2698efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
2709b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu    /**
2719b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu     * This function is no longer used, but keeping it here in case we revive this functionality.
2729b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu     * @throws IOException
2739b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu     */
2749b65e23aebade9eb057070af3757f490a06148d3Yu Ping Hu    @Deprecated
2750565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    private void getAutomaticLookback() throws IOException {
2760565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // If we're using an auto lookback, check how many items in the past week
2770565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // TODO Make the literal ints below constants once we twiddle them a bit
2780565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        int items = getEstimate(Eas.FILTER_1_WEEK);
2790565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        int lookback;
2800565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        if (items > 1050) {
2810565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // Over 150/day, just use one day (smallest)
2820565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_DAY;
2830565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 350 || (items == -1)) {
2840565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 50-150/day, use 3 days (150 to 450 messages synced)
2850565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_3_DAYS;
2860565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 150) {
2870565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 20-50/day, use 1 week (140 to 350 messages synced)
2880565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_WEEK;
2890565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items > 75) {
2900565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // 10-25/day, use 1 week (140 to 350 messages synced)
2910565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_2_WEEKS;
2920565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else if (items < 5) {
2930565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // If there are only a couple, see if it makes sense to get everything
2940565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            items = getEstimate(Eas.FILTER_ALL);
2950565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            if (items >= 0 && items < 100) {
2960565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                lookback = SyncWindow.SYNC_WINDOW_ALL;
2970565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            } else {
2980565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                lookback = SyncWindow.SYNC_WINDOW_1_MONTH;
2990565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3000565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        } else {
3010565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            lookback = SyncWindow.SYNC_WINDOW_1_MONTH;
3020565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3030dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank
30430726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank        // Limit lookback to policy limit
30530726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank        if (mAccount.mPolicyKey > 0) {
30630726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank            Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
30730726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank            if (policy != null) {
30830726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank                int maxLookback = policy.mMaxEmailLookback;
30930726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank                if (maxLookback != 0 && (lookback > policy.mMaxEmailLookback)) {
31030726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank                    lookback = policy.mMaxEmailLookback;
31130726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank                }
31230726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank            }
31330726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank        }
31430726eb1240f82e48182a26de78e69c4763f6d2eMarc Blank
3150565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // Store the new lookback and persist it
3160dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        // TODO Code similar to this is used elsewhere (e.g. MailboxSettings); try to clean this up
3170565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        ContentValues cv = new ContentValues();
3180dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        Uri uri;
3190dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        if (mMailbox.mType == Mailbox.TYPE_INBOX) {
3200dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            mAccount.mSyncLookback = lookback;
3210dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            cv.put(AccountColumns.SYNC_LOOKBACK, lookback);
3220dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            uri = ContentUris.withAppendedId(Account.CONTENT_URI, mAccount.mId);
3230dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        } else {
3240dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            mMailbox.mSyncLookback = lookback;
3250dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            cv.put(MailboxColumns.SYNC_LOOKBACK, lookback);
3260dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank            uri = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId);
3270dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        }
3280dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank        mContentResolver.update(uri, cv, null, null);
3290dfbd9efda459c7eab716a8eca5f908973bc585fMarc Blank
3300565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        CharSequence[] windowEntries = mContext.getResources().getTextArray(
3310565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                R.array.account_settings_mail_window_entries);
332942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy        LogUtils.d(TAG, "Auto lookback: " + windowEntries[lookback]);
3330565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
3340565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
335936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    private static class GetItemEstimateParser extends Parser {
3360565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        private int mEstimate = -1;
3370565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3380565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public GetItemEstimateParser(InputStream in) throws IOException {
3390565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            super(in);
3400565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3410565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
342936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @Override
3430565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public boolean parse() throws IOException {
3440565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            // Loop here through the remaining xml
3450565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
3460565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_GET_ITEM_ESTIMATE) {
3470565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseGetItemEstimate();
3480565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3490565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3500565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3510565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3520565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            return true;
3530565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3540565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3550565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseGetItemEstimate() throws IOException {
3560565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_GET_ITEM_ESTIMATE) != END) {
3570565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_RESPONSE) {
3580565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseResponse();
3590565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3600565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3610565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3620565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3630565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3640565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3650565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseResponse() throws IOException {
3660565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_RESPONSE) != END) {
3670565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_STATUS) {
368942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "GIE status: " + getValue());
3690565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_COLLECTION) {
3700565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    parseCollection();
3710565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3720565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3730565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3740565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3750565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3760565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3770565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        public void parseCollection() throws IOException {
3780565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            while (nextTag(Tags.GIE_COLLECTION) != END) {
3790565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                if (tag == Tags.GIE_CLASS) {
380942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "GIE class: " + getValue());
3810565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_COLLECTION_ID) {
382942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "GIE collectionId: " + getValue());
3830565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else if (tag == Tags.GIE_ESTIMATE) {
3840565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    mEstimate = getValueInt();
385942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                    LogUtils.d(TAG, "GIE estimate: " + mEstimate);
3860565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                } else {
3870565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                    skipTag();
3880565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                }
3890565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
3900565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
3910565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
3920565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
3930565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    /**
3940565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * Return the estimated number of items to be synced in the current mailbox, based on the
3950565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * passed in filter argument
3960565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @param filter an EAS "window" filter
3970565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @return the estimated number of items to be synced, or -1 if unknown
3980565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     * @throws IOException
3990565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank     */
4000565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    private int getEstimate(String filter) throws IOException {
4010565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        Serializer s = new Serializer();
402277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex10 = mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE;
403277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex03 = mService.mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE;
404277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        boolean ex07 = !ex10 && !ex03;
4050565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
4060565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        String className = getCollectionName();
4070565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        String syncKey = getSyncKey();
4080565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        userLog("gie, sending ", className, " syncKey: ", syncKey);
409277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank
4100565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        s.start(Tags.GIE_GET_ITEM_ESTIMATE).start(Tags.GIE_COLLECTIONS);
4110565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        s.start(Tags.GIE_COLLECTION);
412277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        if (ex07) {
413277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2007 likes collection id first
414277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
415277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_FILTER_TYPE, filter);
416277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
417277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        } else if (ex03) {
418277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2003 needs the "class" element
4190565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            s.data(Tags.GIE_CLASS, className);
420277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
421277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
422277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_FILTER_TYPE, filter);
423277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        } else {
424277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            // Exchange 2010 requires the filter inside an OPTIONS container and sync key first
425277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.SYNC_SYNC_KEY, syncKey);
426277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.data(Tags.GIE_COLLECTION_ID, mMailbox.mServerId);
427277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank            s.start(Tags.SYNC_OPTIONS).data(Tags.SYNC_FILTER_TYPE, filter).end();
4280565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
429277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank        s.end().end().end().done(); // GIE_COLLECTION, GIE_COLLECTIONS, GIE_GET_ITEM_ESTIMATE
430277be74f5d0abcc3bb23cd13fae9d628b131e2bfMarc Blank
4310565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        EasResponse resp = mService.sendHttpClientPost("GetItemEstimate",
4320565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank                new ByteArrayEntity(s.toByteArray()), EasSyncService.COMMAND_TIMEOUT);
4336760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank        try {
4346760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            int code = resp.getStatus();
4356760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            if (code == HttpStatus.SC_OK) {
4366760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                if (!resp.isEmpty()) {
4376760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    InputStream is = resp.getInputStream();
4386760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    GetItemEstimateParser gieParser = new GetItemEstimateParser(is);
4396760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    gieParser.parse();
4406760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    // Return the estimated number of items
4416760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    return gieParser.mEstimate;
4426760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                }
4430565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank            }
4446760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank        } finally {
4456760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            resp.close();
4460565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        }
4470565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        // If we can't get an estimate, indicate this...
4480565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank        return -1;
4490565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank    }
4500565fd4f943aa3e5be5e001fb16d2f3d69159de6Marc Blank
4518efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    /**
4528efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     * Return the value of isLooping() as returned from the parser
4538efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     */
4548efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    @Override
4558efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    public boolean isLooping() {
4568efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return mIsLooping;
457ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
458147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
459aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    @Override
460aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public boolean isSyncable() {
461aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank        return true;
462aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    }
463aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
464c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu    public static class EasEmailSyncParser extends AbstractSyncParser {
465ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
466147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
467ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
468ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
469498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final String mMailboxIdAsString;
470ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
471498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Message> newEmails = new ArrayList<Message>();
472498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Message> fetchedEmails = new ArrayList<Message>();
473498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<Long> deletedEmails = new ArrayList<Long>();
474498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        private final ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
47548af7392c82262d17700e3fbdccf3a582809d449Marc Blank
476c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        private final Policy mPolicy;
477c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        private boolean mFetchNeeded = false;
478c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu
47948af7392c82262d17700e3fbdccf3a582809d449Marc Blank        public EasEmailSyncParser(InputStream in, EmailSyncAdapter adapter) throws IOException {
48048af7392c82262d17700e3fbdccf3a582809d449Marc Blank            super(in, adapter);
481ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mMailboxIdAsString = Long.toString(mMailbox.mId);
482c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            if (mAccount.mPolicyKey != 0) {
483c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
484c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            } else {
485c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = null;
486c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            }
487c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        }
488c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu
489c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        public EasEmailSyncParser(final Context context, final ContentResolver resolver,
490c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                final InputStream in, final Mailbox mailbox, final Account account)
491c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                        throws IOException {
492c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            super(context, resolver, in, mailbox, account);
493c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            mMailboxIdAsString = Long.toString(mMailbox.mId);
494c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            if (mAccount.mPolicyKey != 0) {
495c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
496c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            } else {
497c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = null;
498c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            }
499ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
500ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
50177186bb1a174432ef272584374942d8b9228e39cMarc Blank        public EasEmailSyncParser(Parser parser, EmailSyncAdapter adapter) throws IOException {
50226d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            super(parser, adapter);
50326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            mMailboxIdAsString = Long.toString(mMailbox.mId);
504c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            if (mAccount.mPolicyKey != 0) {
505c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
506c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            } else {
507c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mPolicy = null;
508c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            }
509c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        }
510c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu
511c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu        public boolean fetchNeeded() {
512c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            return mFetchNeeded;
51326d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        }
51426d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank
51526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank        public void addData (Message msg, int endingTag) throws IOException {
516ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<Attachment> atts = new ArrayList<Attachment>();
5174f15001bdfd11c79524b4e44d60041967779e763Marc Blank            boolean truncated = false;
518ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
51926d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank            while (nextTag(endingTag) != END) {
520ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
5217c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATTACHMENTS:
5225ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up
523d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        attachmentsParser(atts, msg);
524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5257c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_TO:
52667698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mTo = Address.pack(Address.parse(getValue()));
527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5287c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FROM:
529705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        Address[] froms = Address.parse(getValue());
530705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        if (froms != null && froms.length > 0) {
5314f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mDisplayName = froms[0].toFriendly();
532ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
533705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        msg.mFrom = Address.pack(froms);
534ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5357c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_CC:
53667698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mCc = Address.pack(Address.parse(getValue()));
537ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_REPLY_TO:
53967698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mReplyTo = Address.pack(Address.parse(getValue()));
540ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5417c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DATE_RECEIVED:
5428e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                        msg.mTimeStamp = Utility.parseEmailDateTimeToMillis(getValue());
543ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5447c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_SUBJECT:
545ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mSubject = getValue();
546ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5477c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
548ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mFlagRead = getValueInt() == 1;
549ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5507c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_BODY:
55100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyParser(msg);
55200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
5537c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
554ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        msg.mFlagFavorite = flagParser();
555ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
5564f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_TRUNCATED:
5574f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        truncated = getValueInt() == 1;
5584f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
5594f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_DATA:
5604f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // We get MIME data for EAS 2.5.  First we parse it, then we take the
5614f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // html and/or plain text data and store it in the message
562068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        if (truncated) {
563068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // If the MIME data is truncated, don't bother parsing it, because
564068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // it will take time and throw an exception anyway when EOF is reached
565068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // In this case, we will load the body separately by tagging the message
566068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // "partially loaded".
5673fb34bbdabba3ef5bc5742aa012fd9245944165fMarc Blank                            // Get the data (and ignore it)
5683fb34bbdabba3ef5bc5742aa012fd9245944165fMarc Blank                            getValue();
5694f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            userLog("Partially loaded: ", msg.mServerId);
5704f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mFlagLoaded = Message.FLAG_LOADED_PARTIAL;
5714f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            mFetchNeeded = true;
572068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        } else {
573068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            mimeBodyParser(msg, getValue());
5744f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        }
5754f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
5767c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_BODY:
57767698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        String text = getValue();
57867698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mText = text;
579ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5807531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    case Tags.EMAIL_MESSAGE_CLASS:
5817531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        String messageClass = getValue();
5827531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
583888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
5847531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
585888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
5867531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        }
5877531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        break;
588c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_MEETING_REQUEST:
589c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        meetingRequestParser(msg);
590c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
591e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    case Tags.EMAIL_THREAD_TOPIC:
592e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        msg.mThreadTopic = getValue();
593e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        break;
59477186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.RIGHTS_LICENSE:
59577186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
59677186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
59777186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.EMAIL2_CONVERSATION_ID:
598b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugo                        msg.mServerConversationId =
599b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugo                                Base64.encodeToString(getValueBytes(), Base64.URL_SAFE);
600b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugo                        break;
60177186bb1a174432ef272584374942d8b9228e39cMarc Blank                    case Tags.EMAIL2_CONVERSATION_INDEX:
602b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugo                        // Ignore this byte array since we're not constructing a tree.
60377186bb1a174432ef272584374942d8b9228e39cMarc Blank                        getValueBytes();
60477186bb1a174432ef272584374942d8b9228e39cMarc Blank                        break;
605422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                    case Tags.EMAIL2_LAST_VERB_EXECUTED:
606422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        int val = getValueInt();
607422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        if (val == LAST_VERB_REPLY || val == LAST_VERB_REPLY_ALL) {
608422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            // We aren't required to distinguish between reply and reply all here
609422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            msg.mFlags |= Message.FLAG_REPLIED_TO;
610422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        } else if (val == LAST_VERB_FORWARD) {
611422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                            msg.mFlags |= Message.FLAG_FORWARDED;
612422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        }
613422a3b5f8b8b3efbecaec9bc53860db546bfbe34Marc Blank                        break;
614ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
615ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
616ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
617ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
618ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
619ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (atts.size() > 0) {
620ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mAttachments = atts;
621ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
622e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
623e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            if ((msg.mFlags & Message.FLAG_INCOMING_MEETING_MASK) != 0) {
624e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                String text = TextUtilities.makeSnippetFromHtmlText(
625e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        msg.mText != null ? msg.mText : msg.mHtml);
626e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                if (TextUtils.isEmpty(text)) {
627e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    // Create text for this invitation
628e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    String meetingInfo = msg.mMeetingInfo;
629e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    if (!TextUtils.isEmpty(meetingInfo)) {
630e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        PackedString ps = new PackedString(meetingInfo);
631e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        ContentValues values = new ContentValues();
632e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        putFromMeeting(ps, MeetingInfo.MEETING_LOCATION, values,
633e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                                Events.EVENT_LOCATION);
634e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        String dtstart = ps.get(MeetingInfo.MEETING_DTSTART);
635e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        if (!TextUtils.isEmpty(dtstart)) {
636e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            long startTime = Utility.parseEmailDateTimeToMillis(dtstart);
637e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                            values.put(Events.DTSTART, startTime);
638e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        }
639e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        putFromMeeting(ps, MeetingInfo.MEETING_ALL_DAY, values,
640e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                                Events.ALL_DAY);
641e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        msg.mText = CalendarUtilities.buildMessageTextFromEntityValues(
642e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                                mContext, values, null);
643e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        msg.mHtml = Html.toHtml(new SpannedString(msg.mText));
644e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    }
645e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                }
646e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            }
647e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank        }
648e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank
649942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy        private static void putFromMeeting(PackedString ps, String field, ContentValues values,
650e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                String column) {
651e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            String val = ps.get(field);
652e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            if (!TextUtils.isEmpty(val)) {
653e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                values.put(column, val);
654e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank            }
655ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
65667698e240187c902bed123bf18d342ff25ec75c7Marc Blank
6575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        /**
6585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * Set up the meetingInfo field in the message with various pieces of information gleaned
6595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * from MeetingRequest tags.  This information will be used later to generate an appropriate
6605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * reply email if the user chooses to respond
6615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @param msg the Message being built
6625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @throws IOException
6635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         */
664c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void meetingRequestParser(Message msg) throws IOException {
6655c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            PackedString.Builder packedString = new PackedString.Builder();
666c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_MEETING_REQUEST) != END) {
667c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
6685c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_DTSTAMP:
6695c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTAMP, getValue());
6705c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
671c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_START_TIME:
6725c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTART, getValue());
6735c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
6745c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_END_TIME:
6755c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTEND, getValue());
6765c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
6775c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_ORGANIZER:
6785c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_ORGANIZER_EMAIL, getValue());
6795c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
6805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_LOCATION:
6815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_LOCATION, getValue());
6825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
6835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_GLOBAL_OBJID:
684b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                        packedString.put(MeetingInfo.MEETING_UID,
685b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                                CalendarUtilities.getUidFromGlobalObjId(getValue()));
686c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
687c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_CATEGORIES:
68877186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
689c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
690c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCES:
691c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        recurrencesParser();
692c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
69339fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                    case Tags.EMAIL_RESPONSE_REQUESTED:
69439fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        packedString.put(MeetingInfo.MEETING_RESPONSE_REQUESTED, getValue());
69539fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        break;
696e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                    case Tags.EMAIL_ALL_DAY_EVENT:
697e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        if (getValueInt() == 1) {
69892f04b67d9907753a91ff2386db93c0e07d76633Marc Blank                            packedString.put(MeetingInfo.MEETING_ALL_DAY, "1");
699e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        }
700e6c2456aa6c00ef78c6d1d1621511d7ef8507f83Marc Blank                        break;
701c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
702c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
703c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
704c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
7055c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            if (msg.mSubject != null) {
7065c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                packedString.put(MeetingInfo.MEETING_TITLE, msg.mSubject);
7075c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            }
7085c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            msg.mMeetingInfo = packedString.toString();
709c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
710c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
711c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void recurrencesParser() throws IOException {
712c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_RECURRENCES) != END) {
713c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
714c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCE:
71577186bb1a174432ef272584374942d8b9228e39cMarc Blank                        skipParser(tag);
716f69266d6671aa2c55fd04117a36f74dd17f73067Marc Blank                        break;
717c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
718c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
719c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
720c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
721c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
722c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
723d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank        /**
724d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * Parse a message from the server stream.
725d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * @return the parsed Message
726d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         * @throws IOException
727d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank         */
72877186bb1a174432ef272584374942d8b9228e39cMarc Blank        private Message addParser() throws IOException, CommandStatusException {
729ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Message msg = new Message();
730ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mAccountKey = mAccount.mId;
731ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mMailboxKey = mMailbox.mId;
732f02459a766ddb1727d191daa0aeb559c8f848668Andrew Stadler            msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
733247d8026d6be4609c4d7a8b7be8f2f5a0908e511Marc Blank            // Default to 1 (success) in case we don't get this tag
734247d8026d6be4609c4d7a8b7be8f2f5a0908e511Marc Blank            int status = 1;
735ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7367c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_ADD) != END) {
737ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
7387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
739ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mServerId = getValue();
740ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
741d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    case Tags.SYNC_STATUS:
742d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        status = getValueInt();
743d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        break;
7447c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_APPLICATION_DATA:
74526d9677a1eb48553241897b63a77bbd33daa9f92Marc Blank                        addData(msg, tag);
746ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
747ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
748ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
749ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
750ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
751d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            // For sync, status 1 = success
752d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            if (status != 1) {
75377186bb1a174432ef272584374942d8b9228e39cMarc Blank                throw new CommandStatusException(status, msg.mServerId);
754d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            }
755d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank            return msg;
756ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
757ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
758ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        // For now, we only care about the "active" state
759ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        private Boolean flagParser() throws IOException {
760ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean state = false;
7617c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_FLAG) != END) {
762ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                switch (tag) {
7637c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG_STATUS:
764ce17455fc5abf061e252d495288d0d56404b0b62Marc Blank                        state = getValueInt() == 2;
765ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
766ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    default:
767ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        skipTag();
768ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                }
769ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            }
770ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            return state;
771ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        }
772ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank
77300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void bodyParser(Message msg) throws IOException {
77400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String bodyType = Eas.BODY_PREFERENCE_TEXT;
77500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String body = "";
7767c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_BODY) != END) {
77700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                switch (tag) {
7787c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_TYPE:
77900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyType = getValue();
78000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
7817c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_DATA:
78200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        body = getValue();
78300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
78400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    default:
78500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        skipTag();
78600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
78700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
78800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            // We always ask for TEXT or HTML; there's no third option
78900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
79000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mHtml = body;
79100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
79200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mText = body;
79300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
79400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
79500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
7964f15001bdfd11c79524b4e44d60041967779e763Marc Blank        /**
797068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * Parses untruncated MIME data, saving away the text parts
7984f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param msg the message we're building
7994f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param mimeData the MIME data we've received from the server
800068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * @throws IOException
8014f15001bdfd11c79524b4e44d60041967779e763Marc Blank         */
802942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy        private static void mimeBodyParser(Message msg, String mimeData) throws IOException {
8034f15001bdfd11c79524b4e44d60041967779e763Marc Blank            try {
8044f15001bdfd11c79524b4e44d60041967779e763Marc Blank                ByteArrayInputStream in = new ByteArrayInputStream(mimeData.getBytes());
8054f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // The constructor parses the message
806068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeMessage mimeMessage = new MimeMessage(in);
807068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // Now process body parts & attachments
808068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> viewables = new ArrayList<Part>();
809068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // We'll ignore the attachments, as we'll get them directly from EAS
810068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> attachments = new ArrayList<Part>();
811068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeUtility.collectParts(mimeMessage, viewables, attachments);
812532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                // parseBodyFields fills in the content fields of the Body
813532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                ConversionUtilities.BodyFieldData data =
814532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                        ConversionUtilities.parseBodyFields(viewables);
815068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // But we need them in the message itself for handling during commit()
816532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                msg.setFlags(data.isQuotedReply, data.isQuotedForward);
817532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                msg.mSnippet = data.snippet;
818532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                msg.mHtml = data.htmlContent;
819532ff870ef8e4c70ada7b86694fb4c1fd154af30Andrew Sapperstein                msg.mText = data.textContent;
8204f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } catch (MessagingException e) {
821068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // This would most likely indicate a broken stream
822068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                throw new IOException(e);
8234f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
8244f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
8254f15001bdfd11c79524b4e44d60041967779e763Marc Blank
8265ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        private void attachmentsParser(ArrayList<Attachment> atts, Message msg) throws IOException {
8275ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENTS) != END) {
8285ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                switch (tag) {
8295ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.EMAIL_ATTACHMENT:
8305ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENT:  // BASE_ATTACHMENT is used in EAS 12.0 and up
8315ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        attachmentParser(atts, msg);
8325ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        break;
8335ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    default:
8345ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        skipTag();
8355ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                }
8365ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            }
8375ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        }
8385ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank
83900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void attachmentParser(ArrayList<Attachment> atts, Message msg) throws IOException {
840ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String fileName = null;
841ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String length = null;
842d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            String location = null;
843ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank            boolean isInline = false;
844ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank            String contentId = null;
845ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8467c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENT) != END) {
847ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
8485ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    // We handle both EAS 2.5 and 12.0+ attachments here
8497c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DISPLAY_NAME:
8505ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_DISPLAY_NAME:
851ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        fileName = getValue();
852ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
8537c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_NAME:
8545ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_FILE_REFERENCE:
855d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        location = getValue();
856ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
8577c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_SIZE:
8585ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ESTIMATED_DATA_SIZE:
859ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        length = getValue();
860ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
861ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                    case Tags.BASE_IS_INLINE:
862ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                        isInline = getValueInt() == 1;
863ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                        break;
864ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                    case Tags.BASE_CONTENT_ID:
865ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                        contentId = getValue();
866ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                        break;
867ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
868ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
869ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
870ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
871ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8725ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((fileName != null) && (length != null) && (location != null)) {
873ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Attachment att = new Attachment();
874ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mEncoding = "base64";
875ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mSize = Long.parseLong(length);
876ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mFileName = fileName;
877d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                att.mLocation = location;
87800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                att.mMimeType = getMimeTypeFromFileName(fileName);
879c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                att.mAccountKey = mAccount.mId;
880ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                // Save away the contentId, if we've got one (for inline images); note that the
881ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                // EAS docs appear to be wrong about the tags used; inline images come with
882ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                // contentId rather than contentLocation, when sent from Ex03, Ex07, and Ex10
883ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                if (isInline && !TextUtils.isEmpty(contentId)) {
884ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                    att.mContentId = contentId;
885ea68a80285e175614a7b36adea6fd25f09e3a887Marc Blank                }
886d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                // Check if this attachment can't be downloaded due to an account policy
887d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                if (mPolicy != null) {
888d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                    if (mPolicy.mDontAllowAttachments ||
889d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                            (mPolicy.mMaxAttachmentSize > 0 &&
890d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                                    (att.mSize > mPolicy.mMaxAttachmentSize))) {
891d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                        att.mFlags = Attachment.FLAG_POLICY_DISALLOWS_DOWNLOAD;
892d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                    }
893d1d98cba6f4604c5b88b3c53a09b9741f8c87a54Marc Blank                }
894ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                atts.add(att);
895ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mFlagAttachment = true;
896ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
897ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
898ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
89900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /**
900936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * Returns an appropriate mimetype for the given file name's extension. If a mimetype
901936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * cannot be determined, {@code application/<<x>>} [where @{code <<x>> is the extension,
902936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy         * if it exists or {@code application/octet-stream}].
90300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * At the moment, this is somewhat lame, since many file types aren't recognized
90400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @param fileName the file name to ponder
90500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
90600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // Note: The MimeTypeMap method currently uses a very limited set of mime types
90700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // A bug has been filed against this issue.
90800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        public String getMimeTypeFromFileName(String fileName) {
90900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String mimeType;
91000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            int lastDot = fileName.lastIndexOf('.');
91100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String extension = null;
9125ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
91319fd685351de80c62c9bc7f0f05fe96983a8078dMarc Blank                extension = fileName.substring(lastDot + 1).toLowerCase();
91400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
91500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (extension == null) {
91600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                // A reasonable default for now.
91700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = "application/octet-stream";
91800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
91900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
92000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                if (mimeType == null) {
92100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    mimeType = "application/" + extension;
92200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
92300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
92400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            return mimeType;
92500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
92600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
927ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private Cursor getServerIdCursor(String serverId, String[] projection) {
92834d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            Cursor c = mContentResolver.query(Message.CONTENT_URI, projection,
929c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                    WHERE_SERVER_ID_AND_MAILBOX_KEY, new String[] {serverId, mMailboxIdAsString},
930c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                    null);
93134d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            if (c == null) throw new ProviderUnavailableException();
932bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank            if (c.getCount() > 1) {
933bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                userLog("Multiple messages with the same serverId/mailbox: " + serverId);
934bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank            }
93534d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            return c;
936ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
937ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
938936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
939936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
940048d45641b88c87172074aa5f29b3de307bc3712Marc Blank            while (nextTag(entryTag) != END) {
941ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
9427c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
943ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String serverId = getValue();
944ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        // Find the message in this mailbox with the given serverId
94518e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                        Cursor c = getServerIdCursor(serverId, MESSAGE_ID_SUBJECT_PROJECTION);
946ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
947ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
94849c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                deletes.add(c.getLong(MESSAGE_ID_SUBJECT_ID_COLUMN));
94918e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                if (Eas.USER_LOG) {
95049c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                    userLog("Deleting ", serverId + ", "
95149c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                            + c.getString(MESSAGE_ID_SUBJECT_SUBJECT_COLUMN));
95218e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                }
953ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
954ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
955ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
956ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
957ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
958ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
959ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
960ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
961ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
962ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
963ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
964936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
965ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        class ServerChange {
9669b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final long id;
9679b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Boolean read;
9689b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Boolean flag;
9699b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            final Integer flags;
970ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
9719b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            ServerChange(long _id, Boolean _read, Boolean _flag, Integer _flags) {
972ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                id = _id;
973ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                read = _read;
974ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                flag = _flag;
9759b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                flags = _flags;
976ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
977ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
978ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
979936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        @VisibleForTesting
980936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy        void changeParser(ArrayList<ServerChange> changes) throws IOException {
981ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String serverId = null;
982ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldRead = false;
983ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldFlag = false;
9849b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            int flags = 0;
985ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            long id = 0;
9867c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_CHANGE) != END) {
987ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
9887c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
989ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        serverId = getValue();
990ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
991ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
992ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
9930a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                                userLog("Changing ", serverId);
994ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
995ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                                oldFlag = c.getInt(Message.LIST_FAVORITE_COLUMN) == 1;
9969b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                                flags = c.getInt(Message.LIST_FLAGS_COLUMN);
997ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                id = c.getLong(Message.LIST_ID_COLUMN);
998ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
999ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
1000ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
1001ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
1002ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
1003d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    case Tags.SYNC_APPLICATION_DATA:
10049b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        changeApplicationDataParser(changes, oldRead, oldFlag, flags, id);
1005d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        break;
1006d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    default:
1007d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        skipTag();
1008d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                }
1009d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            }
1010d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        }
1011d82abe7213806e478338af4202b3622f34b5d6feMarc Blank
1012d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        private void changeApplicationDataParser(ArrayList<ServerChange> changes, Boolean oldRead,
10139b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                Boolean oldFlag, int oldFlags, long id) throws IOException {
1014d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean read = null;
1015d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean flag = null;
10169b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            Integer flags = null;
1017d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
1018d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                switch (tag) {
10197c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
1020ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        read = getValueInt() == 1;
1021ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
10227c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
1023ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        flag = flagParser();
1024ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
10259b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    case Tags.EMAIL2_LAST_VERB_EXECUTED:
10269b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        int val = getValueInt();
10279b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        // Clear out the old replied/forward flags and add in the new flag
10289b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        flags = oldFlags & ~(Message.FLAG_REPLIED_TO | Message.FLAG_FORWARDED);
10299b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        if (val == LAST_VERB_REPLY || val == LAST_VERB_REPLY_ALL) {
10309b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            // We aren't required to distinguish between reply and reply all here
10319b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            flags |= Message.FLAG_REPLIED_TO;
10329b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        } else if (val == LAST_VERB_FORWARD) {
10339b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                            flags |= Message.FLAG_FORWARDED;
10349b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        }
10359b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        break;
1036ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
1037ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
1038ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1039ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
10409b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank            // See if there are flag changes re: read, flag (favorite) or replied/forwarded
10415ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if (((read != null) && !oldRead.equals(read)) ||
10429b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    ((flag != null) && !oldFlag.equals(flag)) || (flags != null)) {
10439b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                changes.add(new ServerChange(id, read, flag, flags));
1044ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1045ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1046ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
104700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /* (non-Javadoc)
104800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @see com.android.exchange.adapter.EasContentParser#commandsParser()
104900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
1050147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        @Override
105177186bb1a174432ef272584374942d8b9228e39cMarc Blank        public void commandsParser() throws IOException, CommandStatusException {
10527c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_COMMANDS) != END) {
10537c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                if (tag == Tags.SYNC_ADD) {
1054d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    newEmails.add(addParser());
1055048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                } else if (tag == Tags.SYNC_DELETE || tag == Tags.SYNC_SOFT_DELETE) {
1056048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                    deleteParser(deletedEmails, tag);
10577c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                } else if (tag == Tags.SYNC_CHANGE) {
1058ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    changeParser(changedEmails);
1059ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } else
1060ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    skipTag();
1061ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
106228f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon
106348af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
106448af7392c82262d17700e3fbdccf3a582809d449Marc Blank
106534d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank        /**
106634d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank         * Removed any messages with status 7 (mismatch) from the updatedIdList
106734d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank         * @param endTag the tag we end with
106834d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank         * @throws IOException
106934d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank         */
107034d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank        public void failedUpdateParser(int endTag) throws IOException {
107134d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            // We get serverId and status in the responses
107234d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            String serverId = null;
107334d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            while (nextTag(endTag) != END) {
107434d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                if (tag == Tags.SYNC_STATUS) {
107534d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    int status = getValueInt();
107634d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    if (status == 7 && serverId != null) {
107734d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                        Cursor c = getServerIdCursor(serverId, Message.ID_COLUMN_PROJECTION);
107834d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                        try {
107934d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                            if (c.moveToFirst()) {
108034d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                                Long id = c.getLong(Message.ID_PROJECTION_COLUMN);
1081c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                                userLog("Update of " + serverId + " failed; will retry");
1082c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                                //mUpdatedIdList.remove(id);
1083c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                                //mService.mUpsyncFailed = true;
108434d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                            }
108534d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                        } finally {
108634d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                            c.close();
108734d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                        }
108834d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    }
108934d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                } else if (tag == Tags.SYNC_SERVER_ID) {
109034d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    serverId = getValue();
109134d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                } else {
109234d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    skipTag();
109334d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                }
109434d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank            }
109534d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank        }
109634d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank
109748af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
10984f15001bdfd11c79524b4e44d60041967779e763Marc Blank        public void responsesParser() throws IOException {
10994f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (nextTag(Tags.SYNC_RESPONSES) != END) {
11004f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (tag == Tags.SYNC_ADD || tag == Tags.SYNC_CHANGE || tag == Tags.SYNC_DELETE) {
110134d9cf7433233c1a61ca04cb5be131b9207c00abMarc Blank                    failedUpdateParser(tag);
11024f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } else if (tag == Tags.SYNC_FETCH) {
1103d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    try {
1104d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        fetchedEmails.add(addParser());
110577186bb1a174432ef272584374942d8b9228e39cMarc Blank                    } catch (CommandStatusException sse) {
1106d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        if (sse.mStatus == 8) {
1107d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // 8 = object not found; delete the message from EmailProvider
1108d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // No other status should be seen in a fetch response, except, perhaps,
1109d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            // for some temporary server failure
1110d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                            mContentResolver.delete(Message.CONTENT_URI,
1111c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                                    WHERE_SERVER_ID_AND_MAILBOX_KEY,
1112c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                                    new String[] {sse.mItemId, mMailboxIdAsString});
1113d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                        }
1114d62e26b2ce5a09de6a43c1d2d4f4692eb5aac81aMarc Blank                    }
11154f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
11164f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
111748af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
111848af7392c82262d17700e3fbdccf3a582809d449Marc Blank
111948af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
1120a1e1f139046dfb28ea69c46d392f70764ad6f822Andrew Stadler        public void commit() {
1121527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            commitImpl(0);
1122527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank        }
1123527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank
1124527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank        public void commitImpl(int tryCount) {
1125ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Use a batch operation to handle the changes
1126ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
11274f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1128527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            // Maximum size of message text per fetch
1129527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            int numFetched = fetchedEmails.size();
1130527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            int maxPerFetch = 0;
1131527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            if (numFetched > 0 && tryCount > 0) {
1132527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                // Educated guess that 450000 chars (900k) is ok; 600k is a killer
1133527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                // Remember that when fetching, we're not getting any other data
1134527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                // We'll keep trying, reducing the maximum each time
1135527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                // Realistically, this will rarely exceed 1, and probably never 2
1136527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                maxPerFetch = 450000 / numFetched / tryCount;
1137527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank            }
11384f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (Message msg: fetchedEmails) {
11394f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Find the original message's id (by serverId and mailbox)
11404f15001bdfd11c79524b4e44d60041967779e763Marc Blank                Cursor c = getServerIdCursor(msg.mServerId, EmailContent.ID_PROJECTION);
11414f15001bdfd11c79524b4e44d60041967779e763Marc Blank                String id = null;
11424f15001bdfd11c79524b4e44d60041967779e763Marc Blank                try {
11434f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    if (c.moveToFirst()) {
11444f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        id = c.getString(EmailContent.ID_PROJECTION_COLUMN);
1145bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                        while (c.moveToNext()) {
1146bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                            // This shouldn't happen, but clean up if it does
1147bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                            Long dupId =
1148bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                                    Long.parseLong(c.getString(EmailContent.ID_PROJECTION_COLUMN));
1149bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                            userLog("Delete duplicate with id: " + dupId);
1150bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                            deletedEmails.add(dupId);
1151bf28a9d635f5790fbe158f6891ec84f2b0f3cf9aMarc Blank                        }
11524f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    }
11534f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } finally {
11544f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    c.close();
11554f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
11564f15001bdfd11c79524b4e44d60041967779e763Marc Blank
11574f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // If we find one, we do two things atomically: 1) set the body text for the
11584f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // message, and 2) mark the message loaded (i.e. completely loaded)
11594f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (id != null) {
11604f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    userLog("Fetched body successfully for ", id);
1161c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                    final String[] bindArgument = new String[] {id};
1162527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                    if ((maxPerFetch > 0) && (msg.mText.length() > maxPerFetch)) {
1163527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                        userLog("Truncating message to " + maxPerFetch);
1164527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                        msg.mText = msg.mText.substring(0, maxPerFetch) + "...";
1165527b5952ef438e010ed5a203f12b58a8a2909338Marc Blank                    }
11664f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Body.CONTENT_URI)
1167c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                            .withSelection(Body.MESSAGE_KEY + "=?", bindArgument)
11684f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withValue(Body.TEXT_CONTENT, msg.mText)
11694f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
11704f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Message.CONTENT_URI)
1171c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                            .withSelection(EmailContent.RECORD_ID + "=?", bindArgument)
1172068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            .withValue(Message.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE)
11734f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
11744f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
11754f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
11764f15001bdfd11c79524b4e44d60041967779e763Marc Blank
117777424af660458104b732bdcb718874b17d0cab3aMarc Blank            for (Message msg: newEmails) {
117877424af660458104b732bdcb718874b17d0cab3aMarc Blank                msg.addSaveOps(ops);
1179ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
11804f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1181ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            for (Long id : deletedEmails) {
1182ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                ops.add(ContentProviderOperation.newDelete(
1183ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
1184e951b589c5134a1154ec3743d79236dee54a6519Marc Blank                AttachmentUtilities.deleteAllAttachmentFiles(mContext, mAccount.mId, id);
1185ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
11864f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1187ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (!changedEmails.isEmpty()) {
1188ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Server wins in a conflict...
1189ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                for (ServerChange change : changedEmails) {
1190ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                     ContentValues cv = new ContentValues();
1191ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.read != null) {
1192ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_READ, change.read);
1193ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
1194ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.flag != null) {
1195ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_FAVORITE, change.flag);
1196ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
11979b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    if (change.flags != null) {
11989b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                        cv.put(MessageColumns.FLAGS, change.flags);
11999b243f9a4581a63c6369d0f5a4f695edf2e90e38Marc Blank                    }
1200ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    ops.add(ContentProviderOperation.newUpdate(
1201ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
1202ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .withValues(cv)
1203ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .build());
1204ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1205ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
12068d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
12078d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            // We only want to update the sync key here
12088d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            ContentValues mailboxValues = new ContentValues();
12098d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            mailboxValues.put(Mailbox.SYNC_KEY, mMailbox.mSyncKey);
1210ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ops.add(ContentProviderOperation.newUpdate(
12118d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                    ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
12128d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        .withValues(mailboxValues).build());
1213ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1214c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            try {
1215c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                mContentResolver.applyBatch(EmailContent.AUTHORITY, ops);
1216c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
1217c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            } catch (TransactionTooLargeException e) {
1218942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy                LogUtils.w(TAG, "Transaction failed on fetched message; retrying...");
1219c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                commitImpl(++tryCount);
1220c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu             } catch (RemoteException e) {
1221c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                // There is nothing to be done here; fail by returning null
1222c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu            } catch (OperationApplicationException e) {
1223c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                // There is nothing to be done here; fail by returning null
1224ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
122577424af660458104b732bdcb718874b17d0cab3aMarc Blank        }
122628f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon
122728f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon        @Override
122828f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon        protected void wipe() {
122928f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon            // This file is deprecated, no need to implement this.
123028f3556f92b20b2efb800c1d95c29b04a3bbbb4fMartin Hibdon        }
1231ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1232ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1233ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
1234ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String getCollectionName() {
1235ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return "Email";
1236ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1237ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
12388480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    private void addCleanupOps(ArrayList<ContentProviderOperation> ops) {
12398480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // If we've sent local deletions, clear out the deleted table
12408480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mDeletedIdList) {
12418480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
12428480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
12438480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
12448480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // And same with the updates
12458480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mUpdatedIdList) {
12468480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
12478480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI, id)).build());
12488480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
12499c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank    }
12509c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank
12519c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank    @Override
12529c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank    public void cleanup() {
12539c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1254a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // Delete any moved messages (since we've just synced the mailbox, and no longer need the
1255a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // placeholder message); this prevents duplicates from appearing in the mailbox.
1256a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        ops.add(ContentProviderOperation.newDelete(Message.CONTENT_URI)
1257c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                .withSelection(WHERE_MAILBOX_KEY_AND_MOVED,
1258c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                        new String[] {Long.toString(mMailbox.mId)}).build());
12599c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        // If we've done deletions/updates, clean up the deleted/updated tables
12608480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        if (!mDeletedIdList.isEmpty() || !mUpdatedIdList.isEmpty()) {
12618480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            addCleanupOps(ops);
12629c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        }
12639c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        try {
12649c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank            mContext.getContentResolver()
12659c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank                .applyBatch(EmailContent.AUTHORITY, ops);
12669c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        } catch (RemoteException e) {
12679c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank            // There is nothing to be done here; fail by returning null
12689c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank        } catch (OperationApplicationException e) {
12699c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank            // There is nothing to be done here; fail by returning null
12708480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
12718480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
12728480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
1273942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy    private static String formatTwo(int num) {
127436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        if (num < 10) {
127536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return "0" + (char)('0' + num);
127636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        } else
127736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return Integer.toString(num);
127836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
127936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
128036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    /**
128136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * Create date/time in RFC8601 format.  Oddly enough, for calendar date/time, Microsoft uses
128236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * a different format that excludes the punctuation (this is why I'm not putting this in a
128336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * parent class)
128436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     */
128536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public String formatDateTime(Calendar calendar) {
128636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        StringBuilder sb = new StringBuilder();
128736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        //YYYY-MM-DDTHH:MM:SS.MSSZ
128836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(calendar.get(Calendar.YEAR));
128936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
129036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MONTH) + 1));
129136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
129236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.DAY_OF_MONTH)));
129336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('T');
129436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.HOUR_OF_DAY)));
129536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
129636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MINUTE)));
129736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
129836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.SECOND)));
129936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(".000Z");
130036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        return sb.toString();
130136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
130236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
1303c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /**
1304c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Note that messages in the deleted database preserve the message's unique id; therefore, we
1305c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * can utilize this id to find references to the message.  The only reference situation at this
1306c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * point is in the Body table; it is when sending messages via SmartForward and SmartReply
1307c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
1308942b7d73f2f5b3d6c651e39463e615fe6902a910Scott Kennedy    private static boolean messageReferenced(ContentResolver cr, long id) {
1309c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // See if this id is referenced in a body
1310c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Body.CONTENT_URI, Body.ID_PROJECTION, WHERE_BODY_SOURCE_MESSAGE_KEY,
1311c9e47f85203da0ea3b6e9a49aa2007d1fc6f0814Yu Ping Hu                new String[] {Long.toString(id)}, null);
1312c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        try {
1313c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return c.moveToFirst();
1314c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        } finally {
1315c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            c.close();
13165acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank        }
1317c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
1318c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1319c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /*private*/ /**
1320c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Serialize commands to delete items from the server; as we find items to delete, add their
1321c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * id's to the deletedId's array
1322c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     *
1323c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param s the Serializer we're using to create post data
1324c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param deletedIds ids whose deletions are being sent to the server
1325c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param first whether or not this is the first command being sent
1326c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @return true if SYNC_COMMANDS hasn't been sent (false otherwise)
1327c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @throws IOException
1328c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
1329936b0401ac57e2853915bd3535bbd2ab6869c1bbTodd Kennedy    @VisibleForTesting
1330c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    boolean sendDeletedItems(Serializer s, ArrayList<Long> deletedIds, boolean first)
1331c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            throws IOException {
1332c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
13335acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank
1334ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Find any of our deleted items
1335ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
1336ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
1337ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // We keep track of the list of deleted item id's so that we can remove them from the
1338ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // deleted table after the server receives our command
1339c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        deletedIds.clear();
1340ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
1341ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
13425acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN);
13435acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                // Keep going if there's no serverId
13445acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                if (serverId == null) {
13455acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    continue;
1346c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                // Also check if this message is referenced elsewhere
1347c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                } else if (messageReferenced(cr, c.getLong(Message.CONTENT_ID_COLUMN))) {
1348c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    userLog("Postponing deletion of referenced message: ", serverId);
1349c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    continue;
13505acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                } else if (first) {
13517c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_COMMANDS);
1352ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    first = false;
1353ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1354ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Send the command to delete this message
13555acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
1356c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                deletedIds.add(c.getLong(Message.LIST_ID_COLUMN));
1357ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1358ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
1359ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
1360ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1361ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1362c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank       return first;
1363c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
1364c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1365c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    @Override
1366c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    public boolean sendLocalChanges(Serializer s) throws IOException {
1367c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
1368c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1369a261805b03b853cce662b679da3e16120d521b7eMarc Blank        if (getSyncKey().equals("0")) {
1370a261805b03b853cce662b679da3e16120d521b7eMarc Blank            return false;
1371a261805b03b853cce662b679da3e16120d521b7eMarc Blank        }
1372a261805b03b853cce662b679da3e16120d521b7eMarc Blank
1373c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // Never upsync from these folders
1374c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) {
1375c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return false;
1376c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        }
1377c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
1378c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // This code is split out for unit testing purposes
1379c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true);
1380c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
13814f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (!mFetchRequestList.isEmpty()) {
13824f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Add FETCH commands for messages that need a body (i.e. we didn't find it during
13834f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // our earlier sync; this happens only in EAS 2.5 where the body couldn't be found
13844f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // after parsing the message's MIME data)
13854f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (firstCommand) {
13864f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_COMMANDS);
13874f15001bdfd11c79524b4e44d60041967779e763Marc Blank                firstCommand = false;
13884f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
13894f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (FetchRequest req: mFetchRequestList) {
13904f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_FETCH).data(Tags.SYNC_SERVER_ID, req.serverId).end();
13914f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
13924f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
13934f15001bdfd11c79524b4e44d60041967779e763Marc Blank
139491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // Find our trash mailbox, since deletions will have been moved there...
139591e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        long trashMailboxId =
13960a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
139791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
1398ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do the same now for updated items
1399c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
1400ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
140191e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
140291e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // We keep track of the list of updated item id's as we did above with deleted items
1403ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUpdatedIdList.clear();
1404ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
14059c06bfdc576f9803b45dd009dc4c497366aaaf51Marc Blank            ContentValues cv = new ContentValues();
1406ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
1407ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                long id = c.getLong(Message.LIST_ID_COLUMN);
1408ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Say we've handled this update
1409ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mUpdatedIdList.add(id);
1410ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We have the id of the changed item.  But first, we have to find out its current
1411ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // state, since the updated table saves the opriginal state
1412ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
1413ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        UPDATES_PROJECTION, null, null, null);
1414ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
1415ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // If this item no longer exists (shouldn't be possible), just move along
1416ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (!currentCursor.moveToFirst()) {
1417b2adf8a5aae8647b728441ef4d56f181f26f848eJorge Lugo                        continue;
1418ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
14195acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    // Keep going if there's no serverId
14205acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    String serverId = currentCursor.getString(UPDATES_SERVER_ID_COLUMN);
14215acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    if (serverId == null) {
14225acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                        continue;
14235acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    }
142491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
142536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean flagChange = false;
142636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean readChange = false;
142736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
14284471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    long mailbox = currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN);
14294099db352b1881a403813de8e83ab26f31609df3Marc Blank                    // If the message is now in the trash folder, it has been deleted by the user
14304099db352b1881a403813de8e83ab26f31609df3Marc Blank                    if (mailbox == trashMailboxId) {
14314099db352b1881a403813de8e83ab26f31609df3Marc Blank                         if (firstCommand) {
14324099db352b1881a403813de8e83ab26f31609df3Marc Blank                            s.start(Tags.SYNC_COMMANDS);
14334099db352b1881a403813de8e83ab26f31609df3Marc Blank                            firstCommand = false;
14344099db352b1881a403813de8e83ab26f31609df3Marc Blank                        }
14354099db352b1881a403813de8e83ab26f31609df3Marc Blank                        // Send the command to delete this message
14364099db352b1881a403813de8e83ab26f31609df3Marc Blank                        s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
14374099db352b1881a403813de8e83ab26f31609df3Marc Blank                        // Mark the message as moved (so the copy will be deleted if/when the server
14384099db352b1881a403813de8e83ab26f31609df3Marc Blank                        // version is synced)
14394099db352b1881a403813de8e83ab26f31609df3Marc Blank                        int flags = c.getInt(Message.LIST_FLAGS_COLUMN);
14404099db352b1881a403813de8e83ab26f31609df3Marc Blank                        cv.put(MessageColumns.FLAGS,
14414099db352b1881a403813de8e83ab26f31609df3Marc Blank                                flags | EasSyncService.MESSAGE_FLAG_MOVED_MESSAGE);
14424099db352b1881a403813de8e83ab26f31609df3Marc Blank                        cr.update(ContentUris.withAppendedId(Message.CONTENT_URI, id), cv,
14434099db352b1881a403813de8e83ab26f31609df3Marc Blank                                null, null);
14444099db352b1881a403813de8e83ab26f31609df3Marc Blank                        continue;
14454099db352b1881a403813de8e83ab26f31609df3Marc Blank                    } else if (mailbox != c.getLong(Message.LIST_MAILBOX_KEY_COLUMN)) {
14464471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // The message has moved to another mailbox; add a request for this
14474471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Note: The Sync command doesn't handle moving messages, so we need
14484471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // to handle this as a "request" (similar to meeting response and
14494471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // attachment load)
14504471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mService.addRequest(new MessageMoveRequest(id, mailbox));
14514471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Regardless of other changes that might be made, we don't want to indicate
14524471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // that this message has been updated until the move request has been
14534471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // handled (without this, a crash between the flag upsync and the move
14544471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // would cause the move to be lost)
14554471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mUpdatedIdList.remove(id);
14564471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    }
145736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
145836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // We can only send flag changes to the server in 12.0 or later
14594471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    int flag = 0;
1460d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    if (mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
146136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        flag = currentCursor.getInt(UPDATES_FLAG_COLUMN);
146236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != c.getInt(Message.LIST_FAVORITE_COLUMN)) {
146336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            flagChange = true;
146436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
146536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
146636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
146791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    int read = currentCursor.getInt(UPDATES_READ_COLUMN);
146876eb7b252fc410f5f5d4e90ad54d4bde837de0aaMarc Blank                    if (read != c.getInt(Message.LIST_READ_COLUMN)) {
146936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        readChange = true;
147036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
147136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
147236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (!flagChange && !readChange) {
147336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        // In this case, we've got nothing to send to the server
1474ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        continue;
1475ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
147636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
1477c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    if (firstCommand) {
14787c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.SYNC_COMMANDS);
1479c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                        firstCommand = false;
1480ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
148136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // Send the change to "read" and "favorite" (flagged)
14827c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_CHANGE)
14837c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        .data(Tags.SYNC_SERVER_ID, c.getString(Message.LIST_SERVER_ID_COLUMN))
148436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        .start(Tags.SYNC_APPLICATION_DATA);
148536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (readChange) {
148636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        s.data(Tags.EMAIL_READ, Integer.toString(read));
148736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
148836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // "Flag" is a relatively complex concept in EAS 12.0 and above.  It is not only
148936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // the boolean "favorite" that we think of in Gmail, but it also represents a
149036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // follow up action, which can include a subject, start and due dates, and even
149136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // recurrences.  We don't support any of this as yet, but EAS 12.0 and higher
149236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // require that a flag contain a status, a type, and four date fields, two each
149336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // for start date and end (due) date.
149436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (flagChange) {
149536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != 0) {
149636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Status 2 = set flag
149736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.start(Tags.EMAIL_FLAG).data(Tags.EMAIL_FLAG_STATUS, "2");
149836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // "FollowUp" is the standard type
149936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.EMAIL_FLAG_TYPE, "FollowUp");
150036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            long now = System.currentTimeMillis();
150136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            Calendar calendar =
150236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                                GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
150336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now);
150436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Flags are required to have a start date and end date (duplicated)
150536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // First, we'll set the current date/time in GMT as the start time
150636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            String utc = formatDateTime(calendar);
150736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_START_DATE, utc).data(Tags.TASK_UTC_START_DATE, utc);
150836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // And then we'll use one week from today for completion date
150936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now + 1*WEEKS);
151036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            utc = formatDateTime(calendar);
151136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_DUE_DATE, utc).data(Tags.TASK_UTC_DUE_DATE, utc);
151236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.end();
151336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        } else {
151436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.tag(Tags.EMAIL_FLAG);
151536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
151636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
151736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    s.end().end(); // SYNC_APPLICATION_DATA, SYNC_CHANGE
1518ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
1519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    currentCursor.close();
1520ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1521ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1522ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
1523ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
1524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1525ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1526c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (!firstCommand) {
15277c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end(); // SYNC_COMMANDS
1528ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1529ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return false;
1530ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1531ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1532