EmailSyncAdapter.java revision 134346f5b886e6b53074238546653cdc76bbe868
1ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/*
2ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Copyright (C) 2008-2009 Marc Blank
3ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed to The Android Open Source Project.
4ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
5ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Licensed under the Apache License, Version 2.0 (the "License");
6ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * you may not use this file except in compliance with the License.
7ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * You may obtain a copy of the License at
8ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
9ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *      http://www.apache.org/licenses/LICENSE-2.0
10ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
11ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Unless required by applicable law or agreed to in writing, software
12ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * distributed under the License is distributed on an "AS IS" BASIS,
13ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * See the License for the specific language governing permissions and
15ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * limitations under the License.
16ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
17ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
18ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankpackage com.android.exchange.adapter;
19ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
20068e073ffcb68e06785929208d6a6761b29030f3Marc Blankimport com.android.email.LegacyConversions;
218e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Predaimport com.android.email.Utility;
2267698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.mail.Address;
235c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.email.mail.MeetingInfo;
244f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport com.android.email.mail.MessagingException;
255c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blankimport com.android.email.mail.PackedString;
264f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport com.android.email.mail.Part;
274f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport com.android.email.mail.internet.MimeMessage;
28068e073ffcb68e06785929208d6a6761b29030f3Marc Blankimport com.android.email.mail.internet.MimeUtility;
291f19b92899b3589e5d6303a7dd35ebbf569e00b9Marc Blankimport com.android.email.provider.AttachmentProvider;
30c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blankimport com.android.email.provider.EmailContent;
31134346f5b886e6b53074238546653cdc76bbe868Marc Blankimport com.android.email.provider.EmailProvider;
32894ec76e35053ab73ee9905b6737910e7fab7cd9Andrew Stadlerimport com.android.email.provider.EmailContent.Account;
33c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blankimport com.android.email.provider.EmailContent.AccountColumns;
3467698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Attachment;
35c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blankimport com.android.email.provider.EmailContent.Body;
3667698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Mailbox;
3767698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.Message;
3867698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.MessageColumns;
3967698e240187c902bed123bf18d342ff25ec75c7Marc Blankimport com.android.email.provider.EmailContent.SyncColumns;
40a1e128b4676c1a4fb583b61ea94e561c6045e6f8Marc Blankimport com.android.email.service.MailService;
4100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.Eas;
4200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport com.android.exchange.EasSyncService;
434471a6960d352242cc65bddf7888cc5335840c74Marc Blankimport com.android.exchange.MessageMoveRequest;
44c10a3beef4f048292e6a4ceb31527c5123801517Marc Blankimport com.android.exchange.utility.CalendarUtilities;
45ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
46ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentProviderOperation;
47ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentResolver;
48ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentUris;
49ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.ContentValues;
50ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.content.OperationApplicationException;
51ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.database.Cursor;
5277424af660458104b732bdcb718874b17d0cab3aMarc Blankimport android.net.Uri;
53ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blankimport android.os.RemoteException;
5400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport android.webkit.MimeTypeMap;
55ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
564f15001bdfd11c79524b4e44d60041967779e763Marc Blankimport java.io.ByteArrayInputStream;
5700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.IOException;
5800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.io.InputStream;
5900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.ArrayList;
6036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blankimport java.util.Calendar;
6100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.GregorianCalendar;
6200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blankimport java.util.TimeZone;
63ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
64ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank/**
65ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank * Sync adapter for EAS email
66ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank *
67ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank */
687c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blankpublic class EmailSyncAdapter extends AbstractSyncAdapter {
6900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
7091e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_READ_COLUMN = 0;
7191e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_MAILBOX_KEY_COLUMN = 1;
7291e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final int UPDATES_SERVER_ID_COLUMN = 2;
7336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    private static final int UPDATES_FLAG_COLUMN = 3;
7491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank    private static final String[] UPDATES_PROJECTION =
7536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        {MessageColumns.FLAG_READ, MessageColumns.MAILBOX_KEY, SyncColumns.SERVER_ID,
7636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            MessageColumns.FLAG_FAVORITE};
7749c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank
7849c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_ID_COLUMN = 0;
7949c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank    private static final int MESSAGE_ID_SUBJECT_SUBJECT_COLUMN = 1;
8018e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank    private static final String[] MESSAGE_ID_SUBJECT_PROJECTION =
8118e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank        new String[] { Message.RECORD_ID, MessageColumns.SUBJECT };
8218e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank
83c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    private static final String WHERE_BODY_SOURCE_MESSAGE_KEY = Body.SOURCE_MESSAGE_KEY + "=?";
84a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank    private static final String WHERE_MAILBOX_KEY_AND_MOVED =
85a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        MessageColumns.MAILBOX_KEY + "=? AND (" + MessageColumns.FLAGS + "&" +
86a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        EasSyncService.MESSAGE_FLAG_MOVED_MESSAGE + ")!=0";
874f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String[] FETCH_REQUEST_PROJECTION =
884f15001bdfd11c79524b4e44d60041967779e763Marc Blank        new String[] {EmailContent.RECORD_ID, SyncColumns.SERVER_ID};
894f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_RECORD_ID = 0;
904f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final int FETCH_REQUEST_SERVER_ID = 1;
914f15001bdfd11c79524b4e44d60041967779e763Marc Blank
924f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private static final String EMAIL_WINDOW_SIZE = "5";
9391e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
94c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    String[] mBindArguments = new String[2];
95c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    String[] mBindArgument = new String[1];
96ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
974f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /*package*/ ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
984f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /*package*/ ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
994f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /*package*/ ArrayList<FetchRequest> mFetchRequestList = new ArrayList<FetchRequest>();
1004f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private boolean mFetchNeeded = false;
101ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1028efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    // Holds the parser's value for isLooping()
1038efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    boolean mIsLooping = false;
1048efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
1056f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public EmailSyncAdapter(EasSyncService service) {
1066f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        super(service);
1076f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    }
1086f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank
1096f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    @Override
1106f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank    public void wipe() {
1116f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.CONTENT_URI,
1126f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1136f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.DELETED_CONTENT_URI,
1146f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
1156f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank        mContentResolver.delete(Message.UPDATED_CONTENT_URI,
1166f898deac953e5838fa28f47a16e0fb92bbc81ebMarc Blank                Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
117134346f5b886e6b53074238546653cdc76bbe868Marc Blank        // Delete attachments...
118134346f5b886e6b53074238546653cdc76bbe868Marc Blank        AttachmentProvider.deleteAllMailboxAttachmentFiles(mContext, mAccount.mId, mMailbox.mId);
119ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
120ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1214f15001bdfd11c79524b4e44d60041967779e763Marc Blank    private String getEmailFilter() {
1224f15001bdfd11c79524b4e44d60041967779e763Marc Blank        switch (mAccount.mSyncLookback) {
1234f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_DAY:
1244f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_DAY;
1254f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_3_DAYS:
1264f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_3_DAYS;
1274f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_WEEK:
1284f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
1294f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_2_WEEKS:
1304f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_2_WEEKS;
1314f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_1_MONTH:
1324f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_MONTH;
1334f15001bdfd11c79524b4e44d60041967779e763Marc Blank            case com.android.email.Account.SYNC_WINDOW_ALL:
1344f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_ALL;
1354f15001bdfd11c79524b4e44d60041967779e763Marc Blank            default:
1364f15001bdfd11c79524b4e44d60041967779e763Marc Blank                return Eas.FILTER_1_WEEK;
1374f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1384f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1394f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1404f15001bdfd11c79524b4e44d60041967779e763Marc Blank    /**
1414f15001bdfd11c79524b4e44d60041967779e763Marc Blank     * Holder for fetch request information (record id and server id)
1424f15001bdfd11c79524b4e44d60041967779e763Marc Blank     */
1434f15001bdfd11c79524b4e44d60041967779e763Marc Blank    static class FetchRequest {
1444f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final long messageId;
1454f15001bdfd11c79524b4e44d60041967779e763Marc Blank        final String serverId;
1464f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1474f15001bdfd11c79524b4e44d60041967779e763Marc Blank        FetchRequest(long _messageId, String _serverId) {
1484f15001bdfd11c79524b4e44d60041967779e763Marc Blank            messageId = _messageId;
1494f15001bdfd11c79524b4e44d60041967779e763Marc Blank            serverId = _serverId;
1504f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1514f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
1524f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1534f15001bdfd11c79524b4e44d60041967779e763Marc Blank    @Override
1544f15001bdfd11c79524b4e44d60041967779e763Marc Blank    public void sendSyncOptions(Double protocolVersion, Serializer s)
1554f15001bdfd11c79524b4e44d60041967779e763Marc Blank            throws IOException  {
1564f15001bdfd11c79524b4e44d60041967779e763Marc Blank        mFetchRequestList.clear();
1574f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // Find partially loaded messages; this should typically be a rare occurrence
1584f15001bdfd11c79524b4e44d60041967779e763Marc Blank        Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
1594f15001bdfd11c79524b4e44d60041967779e763Marc Blank                FETCH_REQUEST_PROJECTION,
1604f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.FLAG_LOADED + "=" + Message.FLAG_LOADED_PARTIAL + " AND " +
1614f15001bdfd11c79524b4e44d60041967779e763Marc Blank                MessageColumns.MAILBOX_KEY + "=?",
1624f15001bdfd11c79524b4e44d60041967779e763Marc Blank                new String[] {Long.toString(mMailbox.mId)}, null);
1634f15001bdfd11c79524b4e44d60041967779e763Marc Blank        try {
1644f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Put all of these messages into a list; we'll need both id and server id
1654f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (c.moveToNext()) {
1664f15001bdfd11c79524b4e44d60041967779e763Marc Blank                mFetchRequestList.add(new FetchRequest(c.getLong(FETCH_REQUEST_RECORD_ID),
1674f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        c.getString(FETCH_REQUEST_SERVER_ID)));
1684f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
1694f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } finally {
1704f15001bdfd11c79524b4e44d60041967779e763Marc Blank            c.close();
1714f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
1724f15001bdfd11c79524b4e44d60041967779e763Marc Blank
1734f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // The "empty" case is typical; we send a request for changes, and also specify a sync
1744f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // window, body preference type (HTML for EAS 12.0 and later; MIME for EAS 2.5), and
1754f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // truncation
1764f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If there are fetch requests, we only want the fetches (i.e. no changes from the server)
1774f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // so we turn MIME support off.  Note that we are always using EAS 2.5 if there are fetch
1784f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // requests
1794f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (mFetchRequestList.isEmpty()) {
1804f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.tag(Tags.SYNC_DELETES_AS_MOVES);
1814f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.tag(Tags.SYNC_GET_CHANGES);
1824f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_WINDOW_SIZE, EMAIL_WINDOW_SIZE);
1834f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
1844f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the lookback appropriately (EAS calls this a "filter")
1854f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_FILTER_TYPE, getEmailFilter());
1864f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Set the truncation amount for all classes
1874f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
1884f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.BASE_BODY_PREFERENCE);
1894f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // HTML for email
1904f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_HTML);
1914f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.BASE_TRUNCATION_SIZE, Eas.EAS12_TRUNCATION_SIZE);
1924f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.end();
1934f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } else {
1944f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Use MIME data for EAS 2.5
1954f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_MIME);
1964f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.data(Tags.SYNC_MIME_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
1974f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
1984f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
1994f15001bdfd11c79524b4e44d60041967779e763Marc Blank        } else {
2004f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.start(Tags.SYNC_OPTIONS);
2014f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Ask for plain text, rather than MIME data.  This guarantees that we'll get a usable
2024f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // text body
2034f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_MIME_SUPPORT, Eas.MIME_BODY_PREFERENCE_TEXT);
2044f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.data(Tags.SYNC_TRUNCATION, Eas.EAS2_5_TRUNCATION_SIZE);
2054f15001bdfd11c79524b4e44d60041967779e763Marc Blank            s.end();
2064f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2074f15001bdfd11c79524b4e44d60041967779e763Marc Blank    }
2084f15001bdfd11c79524b4e44d60041967779e763Marc Blank
209ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
21048af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public boolean parse(InputStream is) throws IOException {
21148af7392c82262d17700e3fbdccf3a582809d449Marc Blank        EasEmailSyncParser p = new EasEmailSyncParser(is, this);
2124f15001bdfd11c79524b4e44d60041967779e763Marc Blank        mFetchNeeded = false;
2138efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        boolean res = p.parse();
2148efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        // Hold on to the parser's value for isLooping() to pass back to the service
2158efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        mIsLooping = p.isLooping();
2164f15001bdfd11c79524b4e44d60041967779e763Marc Blank        // If we've need a body fetch, or we've just finished one, return true in order to continue
2174f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (mFetchNeeded || !mFetchRequestList.isEmpty()) {
2184f15001bdfd11c79524b4e44d60041967779e763Marc Blank            return true;
2194f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
2208efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return res;
2218efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    }
2228efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank
2238efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    /**
2248efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     * Return the value of isLooping() as returned from the parser
2258efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank     */
2268efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    @Override
2278efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank    public boolean isLooping() {
2288efd25be4e1db3c0c79aae2ca1b4664b21bb410bMarc Blank        return mIsLooping;
229ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
230147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
231aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    @Override
232aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    public boolean isSyncable() {
233aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank        return true;
234aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank    }
235aa288fe7ccbd28abcf990ce8337f2da677a1d370Marc Blank
236368adeb5779fed5d64770d2131125dd93e43ab78Marc Blank    public class EasEmailSyncParser extends AbstractSyncParser {
237ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
238147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
239ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
240ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
241ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private String mMailboxIdAsString;
242ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
24348af7392c82262d17700e3fbdccf3a582809d449Marc Blank        ArrayList<Message> newEmails = new ArrayList<Message>();
2444f15001bdfd11c79524b4e44d60041967779e763Marc Blank        ArrayList<Message> fetchedEmails = new ArrayList<Message>();
24548af7392c82262d17700e3fbdccf3a582809d449Marc Blank        ArrayList<Long> deletedEmails = new ArrayList<Long>();
24648af7392c82262d17700e3fbdccf3a582809d449Marc Blank        ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
24748af7392c82262d17700e3fbdccf3a582809d449Marc Blank
24848af7392c82262d17700e3fbdccf3a582809d449Marc Blank        public EasEmailSyncParser(InputStream in, EmailSyncAdapter adapter) throws IOException {
24948af7392c82262d17700e3fbdccf3a582809d449Marc Blank            super(in, adapter);
250ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            mMailboxIdAsString = Long.toString(mMailbox.mId);
251ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
252ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
253ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        public void addData (Message msg) throws IOException {
254ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<Attachment> atts = new ArrayList<Attachment>();
2554f15001bdfd11c79524b4e44d60041967779e763Marc Blank            boolean truncated = false;
256ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
2577c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
258ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
2597c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATTACHMENTS:
2605ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENTS: // BASE_ATTACHMENTS is used in EAS 12.0 and up
261d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        attachmentsParser(atts, msg);
262ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2637c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_TO:
26467698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mTo = Address.pack(Address.parse(getValue()));
265ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2667c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FROM:
267705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        Address[] froms = Address.parse(getValue());
268705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        if (froms != null && froms.length > 0) {
2694f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mDisplayName = froms[0].toFriendly();
270ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
271705a309bd78efd77469ac90a57849619de3317e3Mihai Preda                        msg.mFrom = Address.pack(froms);
272ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2737c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_CC:
27467698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mCc = Address.pack(Address.parse(getValue()));
275ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2767c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_REPLY_TO:
27767698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mReplyTo = Address.pack(Address.parse(getValue()));
278ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DATE_RECEIVED:
2808e26c42accbaf72eff6694173496aba0e6aa37f6Mihai Preda                        msg.mTimeStamp = Utility.parseEmailDateTimeToMillis(getValue());
281ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2827c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_SUBJECT:
283ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mSubject = getValue();
284ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2857c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
286ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mFlagRead = getValueInt() == 1;
287ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
2887c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_BODY:
28900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyParser(msg);
29000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
2917c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
292ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        msg.mFlagFavorite = flagParser();
293ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
2944f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_TRUNCATED:
2954f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        truncated = getValueInt() == 1;
2964f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
2974f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    case Tags.EMAIL_MIME_DATA:
2984f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // We get MIME data for EAS 2.5.  First we parse it, then we take the
2994f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        // html and/or plain text data and store it in the message
300068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        if (truncated) {
301068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // If the MIME data is truncated, don't bother parsing it, because
302068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // it will take time and throw an exception anyway when EOF is reached
303068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // In this case, we will load the body separately by tagging the message
304068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            // "partially loaded".
3054f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            userLog("Partially loaded: ", msg.mServerId);
3064f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            msg.mFlagLoaded = Message.FLAG_LOADED_PARTIAL;
3074f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            mFetchNeeded = true;
308068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                        } else {
309068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            mimeBodyParser(msg, getValue());
3104f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        }
3114f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        break;
3127c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_BODY:
31367698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        String text = getValue();
31467698e240187c902bed123bf18d342ff25ec75c7Marc Blank                        msg.mText = text;
315ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
3167531be7774769c84b499b1de5dc46da3a9468316Marc Blank                    case Tags.EMAIL_MESSAGE_CLASS:
3177531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        String messageClass = getValue();
3187531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        if (messageClass.equals("IPM.Schedule.Meeting.Request")) {
319888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_INVITE;
3207531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        } else if (messageClass.equals("IPM.Schedule.Meeting.Canceled")) {
321888ddc50a9f6a05d2f4076bada11085ddcfb8aefAndrew Stadler                            msg.mFlags |= Message.FLAG_INCOMING_MEETING_CANCEL;
3227531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        }
3237531be7774769c84b499b1de5dc46da3a9468316Marc Blank                        break;
324c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_MEETING_REQUEST:
325c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        meetingRequestParser(msg);
326c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
327ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
328ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
329ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
330ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
331ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
332ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (atts.size() > 0) {
333ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mAttachments = atts;
334ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
335ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
33667698e240187c902bed123bf18d342ff25ec75c7Marc Blank
3375c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank        /**
3385c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * Set up the meetingInfo field in the message with various pieces of information gleaned
3395c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * from MeetingRequest tags.  This information will be used later to generate an appropriate
3405c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * reply email if the user chooses to respond
3415c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @param msg the Message being built
3425c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         * @throws IOException
3435c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank         */
344c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void meetingRequestParser(Message msg) throws IOException {
3455c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            PackedString.Builder packedString = new PackedString.Builder();
346c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_MEETING_REQUEST) != END) {
347c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
3485c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_DTSTAMP:
3495c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTAMP, getValue());
3505c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
351c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_START_TIME:
3525c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTSTART, getValue());
3535c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
3545c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_END_TIME:
3555c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_DTEND, getValue());
3565c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
3575c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_ORGANIZER:
3585c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_ORGANIZER_EMAIL, getValue());
3595c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
3605c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_LOCATION:
3615c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        packedString.put(MeetingInfo.MEETING_LOCATION, getValue());
3625c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                        break;
3635c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                    case Tags.EMAIL_GLOBAL_OBJID:
364b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                        packedString.put(MeetingInfo.MEETING_UID,
365b94d16528fc9c7f5dbb5c75c059f76cee2070c09Marc Blank                                CalendarUtilities.getUidFromGlobalObjId(getValue()));
366c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
367c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_CATEGORIES:
368c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        nullParser();
369c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
370c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCES:
371c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        recurrencesParser();
372c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        break;
37339fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                    case Tags.EMAIL_RESPONSE_REQUESTED:
37439fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        packedString.put(MeetingInfo.MEETING_RESPONSE_REQUESTED, getValue());
37539fd7f258e1ca1a8d83dd53aff6da3ebb5007456Marc Blank                        break;
376c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
377c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
378c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
379c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
3805c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            if (msg.mSubject != null) {
3815c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank                packedString.put(MeetingInfo.MEETING_TITLE, msg.mSubject);
3825c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            }
3835c6e14ab2f2e4c5dfc97cdeaedcc105159a9f29cMarc Blank            msg.mMeetingInfo = packedString.toString();
384c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
385c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
386c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void nullParser() throws IOException {
387c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_CATEGORIES) != END) {
388c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                skipTag();
389c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
390c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
391c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
392c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        private void recurrencesParser() throws IOException {
393c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            while (nextTag(Tags.EMAIL_RECURRENCES) != END) {
394c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                switch (tag) {
395c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    case Tags.EMAIL_RECURRENCE:
396c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        nullParser();
397f69266d6671aa2c55fd04117a36f74dd17f73067Marc Blank                        break;
398c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                    default:
399c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                        skipTag();
400c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank                }
401c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank            }
402c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank        }
403c10a3beef4f048292e6a4ceb31527c5123801517Marc Blank
40400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void addParser(ArrayList<Message> emails) throws IOException {
405ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            Message msg = new Message();
406ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mAccountKey = mAccount.mId;
407ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            msg.mMailboxKey = mMailbox.mId;
408f02459a766ddb1727d191daa0aeb559c8f848668Andrew Stadler            msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
409ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
4107c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_ADD) != END) {
411ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
4127c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
413ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        msg.mServerId = getValue();
414ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
4157c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_APPLICATION_DATA:
416ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        addData(msg);
417ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
418ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
419ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
420ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
421ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
422ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            emails.add(msg);
423ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
424ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
425ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        // For now, we only care about the "active" state
426ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        private Boolean flagParser() throws IOException {
427ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean state = false;
4287c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_FLAG) != END) {
429ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                switch (tag) {
4307c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG_STATUS:
431ce17455fc5abf061e252d495288d0d56404b0b62Marc Blank                        state = getValueInt() == 2;
432ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
433ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    default:
434ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        skipTag();
435ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                }
436ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            }
437ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            return state;
438ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank        }
439ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank
44000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void bodyParser(Message msg) throws IOException {
44100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String bodyType = Eas.BODY_PREFERENCE_TEXT;
44200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String body = "";
4437c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_BODY) != END) {
44400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                switch (tag) {
4457c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_TYPE:
44600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        bodyType = getValue();
44700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
4487c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.BASE_DATA:
44900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        body = getValue();
45000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        break;
45100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    default:
45200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                        skipTag();
45300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
45400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
45500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            // We always ask for TEXT or HTML; there's no third option
45600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
45700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mHtml = body;
45800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
45900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                msg.mText = body;
46000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
46100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
46200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
4634f15001bdfd11c79524b4e44d60041967779e763Marc Blank        /**
464068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * Parses untruncated MIME data, saving away the text parts
4654f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param msg the message we're building
4664f15001bdfd11c79524b4e44d60041967779e763Marc Blank         * @param mimeData the MIME data we've received from the server
467068e073ffcb68e06785929208d6a6761b29030f3Marc Blank         * @throws IOException
4684f15001bdfd11c79524b4e44d60041967779e763Marc Blank         */
469068e073ffcb68e06785929208d6a6761b29030f3Marc Blank        private void mimeBodyParser(Message msg, String mimeData) throws IOException {
4704f15001bdfd11c79524b4e44d60041967779e763Marc Blank            try {
4714f15001bdfd11c79524b4e44d60041967779e763Marc Blank                ByteArrayInputStream in = new ByteArrayInputStream(mimeData.getBytes());
4724f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // The constructor parses the message
473068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeMessage mimeMessage = new MimeMessage(in);
474068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // Now process body parts & attachments
475068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> viewables = new ArrayList<Part>();
476068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // We'll ignore the attachments, as we'll get them directly from EAS
477068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                ArrayList<Part> attachments = new ArrayList<Part>();
478068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                MimeUtility.collectParts(mimeMessage, viewables, attachments);
479068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                Body tempBody = new Body();
480068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // updateBodyFields fills in the content fields of the Body
481068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                LegacyConversions.updateBodyFields(tempBody, msg, viewables);
482068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // But we need them in the message itself for handling during commit()
483068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                msg.mHtml = tempBody.mHtmlContent;
484068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                msg.mText = tempBody.mTextContent;
4854f15001bdfd11c79524b4e44d60041967779e763Marc Blank            } catch (MessagingException e) {
486068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                // This would most likely indicate a broken stream
487068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                throw new IOException(e);
4884f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
4894f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
4904f15001bdfd11c79524b4e44d60041967779e763Marc Blank
4915ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        private void attachmentsParser(ArrayList<Attachment> atts, Message msg) throws IOException {
4925ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENTS) != END) {
4935ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                switch (tag) {
4945ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.EMAIL_ATTACHMENT:
4955ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ATTACHMENT:  // BASE_ATTACHMENT is used in EAS 12.0 and up
4965ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        attachmentParser(atts, msg);
4975ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        break;
4985ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    default:
4995ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                        skipTag();
5005ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                }
5015ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            }
5025ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank        }
5035ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank
50400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        private void attachmentParser(ArrayList<Attachment> atts, Message msg) throws IOException {
505ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String fileName = null;
506ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String length = null;
507d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank            String location = null;
508ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5097c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.EMAIL_ATTACHMENT) != END) {
510ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
5115ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    // We handle both EAS 2.5 and 12.0+ attachments here
5127c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_DISPLAY_NAME:
5135ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_DISPLAY_NAME:
514ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        fileName = getValue();
515ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5167c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_NAME:
5175ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_FILE_REFERENCE:
518d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                        location = getValue();
519ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
5207c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_ATT_SIZE:
5215ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    case Tags.BASE_ESTIMATED_DATA_SIZE:
522ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        length = getValue();
523ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
524ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
525ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
526ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
527ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
528ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
5295ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((fileName != null) && (length != null) && (location != null)) {
530ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Attachment att = new Attachment();
531ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mEncoding = "base64";
532ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mSize = Long.parseLong(length);
533ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                att.mFileName = fileName;
534d95115d72be6797835e1286fb8f0d2e5a01cf3a8Marc Blank                att.mLocation = location;
53500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                att.mMimeType = getMimeTypeFromFileName(fileName);
536ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                atts.add(att);
537ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                msg.mFlagAttachment = true;
538ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
539ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
540ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
54100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /**
54200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * Try to determine a mime type from a file name, defaulting to application/x, where x
54300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * is either the extension or (if none) octet-stream
54400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * At the moment, this is somewhat lame, since many file types aren't recognized
54500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @param fileName the file name to ponder
54600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @return
54700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
54800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // Note: The MimeTypeMap method currently uses a very limited set of mime types
54900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        // A bug has been filed against this issue.
55000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        public String getMimeTypeFromFileName(String fileName) {
55100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String mimeType;
55200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            int lastDot = fileName.lastIndexOf('.');
55300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            String extension = null;
5545ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
55519fd685351de80c62c9bc7f0f05fe96983a8078dMarc Blank                extension = fileName.substring(lastDot + 1).toLowerCase();
55600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
55700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            if (extension == null) {
55800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                // A reasonable default for now.
55900d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = "application/octet-stream";
56000d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            } else {
56100d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
56200d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                if (mimeType == null) {
56300d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                    mimeType = "application/" + extension;
56400d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank                }
56500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            }
56600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank            return mimeType;
56700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        }
56800d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank
569ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        private Cursor getServerIdCursor(String serverId, String[] projection) {
570c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            mBindArguments[0] = serverId;
571c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            mBindArguments[1] = mMailboxIdAsString;
572ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            return mContentResolver.query(Message.CONTENT_URI, projection,
573c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    WHERE_SERVER_ID_AND_MAILBOX_KEY, mBindArguments, null);
574ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
575ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
57602e3436296ad5a638a1b6f349555e39964c6d13dMarc Blank        /*package*/ void deleteParser(ArrayList<Long> deletes, int entryTag) throws IOException {
577048d45641b88c87172074aa5f29b3de307bc3712Marc Blank            while (nextTag(entryTag) != END) {
578ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
5797c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
580ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        String serverId = getValue();
581ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        // Find the message in this mailbox with the given serverId
58218e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                        Cursor c = getServerIdCursor(serverId, MESSAGE_ID_SUBJECT_PROJECTION);
583ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
584ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
58549c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                deletes.add(c.getLong(MESSAGE_ID_SUBJECT_ID_COLUMN));
58618e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                if (Eas.USER_LOG) {
58749c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                    userLog("Deleting ", serverId + ", "
58849c739b7b7e0663b9a292b0d0ec966814aebe42aMarc Blank                                            + c.getString(MESSAGE_ID_SUBJECT_SUBJECT_COLUMN));
58918e1e20e3c5e098fd4c038349dddb6112aa130edMarc Blank                                }
590ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
591ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
592ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
593ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
594ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
595ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
596ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
597ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
598ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
599ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
600ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
601ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        class ServerChange {
602ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            long id;
603ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean read;
604ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean flag;
605ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
606ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            ServerChange(long _id, Boolean _read, Boolean _flag) {
607ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                id = _id;
608ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                read = _read;
609ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                flag = _flag;
610ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
611ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
612ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
61302e3436296ad5a638a1b6f349555e39964c6d13dMarc Blank        /*package*/ void changeParser(ArrayList<ServerChange> changes) throws IOException {
614ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            String serverId = null;
615ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldRead = false;
616ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank            Boolean oldFlag = false;
617ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            long id = 0;
6187c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_CHANGE) != END) {
619ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                switch (tag) {
6207c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.SYNC_SERVER_ID:
621ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        serverId = getValue();
622ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
623ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        try {
624ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            if (c.moveToFirst()) {
6250a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                                userLog("Changing ", serverId);
626ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
627ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                                oldFlag = c.getInt(Message.LIST_FAVORITE_COLUMN) == 1;
628ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                id = c.getLong(Message.LIST_ID_COLUMN);
629ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            }
630ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        } finally {
631ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            c.close();
632ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        }
633ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
634d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    case Tags.SYNC_APPLICATION_DATA:
635d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        changeApplicationDataParser(changes, oldRead, oldFlag, id);
636d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        break;
637d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                    default:
638d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                        skipTag();
639d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                }
640d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            }
641d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        }
642d82abe7213806e478338af4202b3622f34b5d6feMarc Blank
643d82abe7213806e478338af4202b3622f34b5d6feMarc Blank        private void changeApplicationDataParser(ArrayList<ServerChange> changes, Boolean oldRead,
644d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                Boolean oldFlag, long id) throws IOException {
645d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean read = null;
646d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            Boolean flag = null;
647d82abe7213806e478338af4202b3622f34b5d6feMarc Blank            while (nextTag(Tags.SYNC_APPLICATION_DATA) != END) {
648d82abe7213806e478338af4202b3622f34b5d6feMarc Blank                switch (tag) {
6497c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_READ:
650ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        read = getValueInt() == 1;
651ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        break;
6527c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    case Tags.EMAIL_FLAG:
653ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        flag = flagParser();
654ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        break;
655ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    default:
656ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        skipTag();
657ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
658ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
6595ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank            if (((read != null) && !oldRead.equals(read)) ||
6605ba0321df8ea8d854de76263348ba26b0a8cff16Marc Blank                    ((flag != null) && !oldFlag.equals(flag))) {
661ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                changes.add(new ServerChange(id, read, flag));
662ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
663ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
664ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
66500d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank        /* (non-Javadoc)
66600d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         * @see com.android.exchange.adapter.EasContentParser#commandsParser()
66700d91b2e12d65df06916afdc4bebca67fd27214cMarc Blank         */
668147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank        @Override
669ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        public void commandsParser() throws IOException {
6707c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            while (nextTag(Tags.SYNC_COMMANDS) != END) {
6717c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                if (tag == Tags.SYNC_ADD) {
672ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    addParser(newEmails);
6730a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
674048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                } else if (tag == Tags.SYNC_DELETE || tag == Tags.SYNC_SOFT_DELETE) {
675048d45641b88c87172074aa5f29b3de307bc3712Marc Blank                    deleteParser(deletedEmails, tag);
6760a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
6777c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                } else if (tag == Tags.SYNC_CHANGE) {
678ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    changeParser(changedEmails);
6790a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    incrementChangeCount();
680ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } else
681ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    skipTag();
682ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
68348af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
68448af7392c82262d17700e3fbdccf3a582809d449Marc Blank
68548af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
6864f15001bdfd11c79524b4e44d60041967779e763Marc Blank        public void responsesParser() throws IOException {
6874f15001bdfd11c79524b4e44d60041967779e763Marc Blank            while (nextTag(Tags.SYNC_RESPONSES) != END) {
6884f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (tag == Tags.SYNC_ADD || tag == Tags.SYNC_CHANGE || tag == Tags.SYNC_DELETE) {
6894f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    // We can ignore all of these
6904f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } else if (tag == Tags.SYNC_FETCH) {
6914f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    addParser(fetchedEmails);
6924f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
6934f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
69448af7392c82262d17700e3fbdccf3a582809d449Marc Blank        }
69548af7392c82262d17700e3fbdccf3a582809d449Marc Blank
69648af7392c82262d17700e3fbdccf3a582809d449Marc Blank        @Override
697a1e1f139046dfb28ea69c46d392f70764ad6f822Andrew Stadler        public void commit() {
69848af7392c82262d17700e3fbdccf3a582809d449Marc Blank            int notifyCount = 0;
699ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
700ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // Use a batch operation to handle the changes
701ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            // TODO New mail notifications?  Who looks for these?
702ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
7034f15001bdfd11c79524b4e44d60041967779e763Marc Blank
7044f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (Message msg: fetchedEmails) {
7054f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // If there's really no text, we're done here
7064f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (msg.mText == null) continue;
7074f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // Find the original message's id (by serverId and mailbox)
7084f15001bdfd11c79524b4e44d60041967779e763Marc Blank                Cursor c = getServerIdCursor(msg.mServerId, EmailContent.ID_PROJECTION);
7094f15001bdfd11c79524b4e44d60041967779e763Marc Blank                String id = null;
7104f15001bdfd11c79524b4e44d60041967779e763Marc Blank                try {
7114f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    if (c.moveToFirst()) {
7124f15001bdfd11c79524b4e44d60041967779e763Marc Blank                        id = c.getString(EmailContent.ID_PROJECTION_COLUMN);
7134f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    }
7144f15001bdfd11c79524b4e44d60041967779e763Marc Blank                } finally {
7154f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    c.close();
7164f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
7174f15001bdfd11c79524b4e44d60041967779e763Marc Blank
7184f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // If we find one, we do two things atomically: 1) set the body text for the
7194f15001bdfd11c79524b4e44d60041967779e763Marc Blank                // message, and 2) mark the message loaded (i.e. completely loaded)
7204f15001bdfd11c79524b4e44d60041967779e763Marc Blank                if (id != null) {
7214f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    userLog("Fetched body successfully for ", id);
7224f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    mBindArgument[0] = id;
7234f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Body.CONTENT_URI)
7244f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withSelection(Body.MESSAGE_KEY + "=?", mBindArgument)
7254f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withValue(Body.TEXT_CONTENT, msg.mText)
7264f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
7274f15001bdfd11c79524b4e44d60041967779e763Marc Blank                    ops.add(ContentProviderOperation.newUpdate(Message.CONTENT_URI)
7284f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .withSelection(EmailContent.RECORD_ID + "=?", mBindArgument)
729068e073ffcb68e06785929208d6a6761b29030f3Marc Blank                            .withValue(Message.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE)
7304f15001bdfd11c79524b4e44d60041967779e763Marc Blank                            .build());
7314f15001bdfd11c79524b4e44d60041967779e763Marc Blank                }
7324f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
7334f15001bdfd11c79524b4e44d60041967779e763Marc Blank
73477424af660458104b732bdcb718874b17d0cab3aMarc Blank            for (Message msg: newEmails) {
73577424af660458104b732bdcb718874b17d0cab3aMarc Blank                if (!msg.mFlagRead) {
73677424af660458104b732bdcb718874b17d0cab3aMarc Blank                    notifyCount++;
73777424af660458104b732bdcb718874b17d0cab3aMarc Blank                }
73877424af660458104b732bdcb718874b17d0cab3aMarc Blank                msg.addSaveOps(ops);
739ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
7404f15001bdfd11c79524b4e44d60041967779e763Marc Blank
741ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            for (Long id : deletedEmails) {
742ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                ops.add(ContentProviderOperation.newDelete(
743ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
7441f19b92899b3589e5d6303a7dd35ebbf569e00b9Marc Blank                AttachmentProvider.deleteAllAttachmentFiles(mContext, mAccount.mId, id);
745ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
7464f15001bdfd11c79524b4e44d60041967779e763Marc Blank
747ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            if (!changedEmails.isEmpty()) {
748ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Server wins in a conflict...
749ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                for (ServerChange change : changedEmails) {
750ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                     ContentValues cv = new ContentValues();
751ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.read != null) {
752ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_READ, change.read);
753ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
754ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    if (change.flag != null) {
755ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                        cv.put(MessageColumns.FLAG_FAVORITE, change.flag);
756ede29869a74740fd93bd74aa42d085d3704699cfMarc Blank                    }
757ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    ops.add(ContentProviderOperation.newUpdate(
758ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                            ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
759ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .withValues(cv)
760ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                                .build());
761ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
762ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
7638d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank
7648d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            // We only want to update the sync key here
7658d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            ContentValues mailboxValues = new ContentValues();
7668d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank            mailboxValues.put(Mailbox.SYNC_KEY, mMailbox.mSyncKey);
767ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            ops.add(ContentProviderOperation.newUpdate(
7688d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                    ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
7698d12fd6f9d0993a8eaf7777d57ad04d80dfd2dd1Marc Blank                        .withValues(mailboxValues).build());
770ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
7718480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            addCleanupOps(ops);
772147e03d50b8a793d58d67917af4bc6333f8afac1Marc Blank
7731b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            // No commits if we're stopped
7741b275b9408d5b856e2482fa3951827489e9585ccMarc Blank            synchronized (mService.getSynchronizer()) {
7751b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                if (mService.isStopped()) return;
7761b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                try {
7770a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
7780a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank                    userLog(mMailbox.mDisplayName, " SyncKey saved as: ", mMailbox.mSyncKey);
7791b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                } catch (RemoteException e) {
7801b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    // There is nothing to be done here; fail by returning null
7811b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                } catch (OperationApplicationException e) {
7821b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                    // There is nothing to be done here; fail by returning null
7831b275b9408d5b856e2482fa3951827489e9585ccMarc Blank                }
784ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
7858480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
78677424af660458104b732bdcb718874b17d0cab3aMarc Blank            if (notifyCount > 0) {
787c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                // Use the new atomic add URI in EmailProvider
788c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                // We could add this to the operations being done, but it's not strictly
789c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                // speaking necessary, as the previous batch preserves the integrity of the
790c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                // database, whereas this is purely for notification purposes, and is itself atomic
791c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                ContentValues cv = new ContentValues();
792c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                cv.put(EmailContent.FIELD_COLUMN_NAME, AccountColumns.NEW_MESSAGE_COUNT);
793c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                cv.put(EmailContent.ADD_COLUMN_NAME, notifyCount);
794c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                Uri uri = ContentUris.withAppendedId(Account.ADD_TO_FIELD_URI, mAccount.mId);
795c88a53832acb73bc6959cd544b2d6c7a2f52c367Marc Blank                mContentResolver.update(uri, cv, null, null);
796a1e128b4676c1a4fb583b61ea94e561c6045e6f8Marc Blank                MailService.actionNotifyNewMessages(mContext, mAccount.mId);
797894ec76e35053ab73ee9905b6737910e7fab7cd9Andrew Stadler            }
79877424af660458104b732bdcb718874b17d0cab3aMarc Blank        }
799ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
800ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
801ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    @Override
802ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    public String getCollectionName() {
803ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return "Email";
804ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
805ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
8068480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    private void addCleanupOps(ArrayList<ContentProviderOperation> ops) {
8078480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // If we've sent local deletions, clear out the deleted table
8088480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mDeletedIdList) {
8098480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
8108480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
8118480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
8128480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        // And same with the updates
8138480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        for (Long id: mUpdatedIdList) {
8148480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ops.add(ContentProviderOperation.newDelete(
8158480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    ContentUris.withAppendedId(Message.UPDATED_CONTENT_URI, id)).build());
8168480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
817a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // Delete any moved messages (since we've just synced the mailbox, and no longer need the
818a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        // placeholder message); this prevents duplicates from appearing in the mailbox.
819a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        mBindArgument[0] = Long.toString(mMailbox.mId);
820a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank        ops.add(ContentProviderOperation.newDelete(Message.CONTENT_URI)
821a11e0c0d45e4053824c72dc9042d9b76005da4a6Marc Blank                .withSelection(WHERE_MAILBOX_KEY_AND_MOVED, mBindArgument).build());
8228480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
8238480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
8248480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    @Override
82548af7392c82262d17700e3fbdccf3a582809d449Marc Blank    public void cleanup() {
8268480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        if (!mDeletedIdList.isEmpty() || !mUpdatedIdList.isEmpty()) {
8278480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
8288480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            addCleanupOps(ops);
8298480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            try {
83048af7392c82262d17700e3fbdccf3a582809d449Marc Blank                mContext.getContentResolver()
8318480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                    .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
8328480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            } catch (RemoteException e) {
8338480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                // There is nothing to be done here; fail by returning null
8348480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            } catch (OperationApplicationException e) {
8358480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank                // There is nothing to be done here; fail by returning null
8368480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank            }
8378480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank        }
8388480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank    }
8398480f5bc2eb612920f7e17312a693b4d8c26f14bMarc Blank
84036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    private String formatTwo(int num) {
84136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        if (num < 10) {
84236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return "0" + (char)('0' + num);
84336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        } else
84436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank            return Integer.toString(num);
84536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
84636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
84736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    /**
84836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * Create date/time in RFC8601 format.  Oddly enough, for calendar date/time, Microsoft uses
84936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * a different format that excludes the punctuation (this is why I'm not putting this in a
85036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     * parent class)
85136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank     */
85236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    public String formatDateTime(Calendar calendar) {
85336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        StringBuilder sb = new StringBuilder();
85436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        //YYYY-MM-DDTHH:MM:SS.MSSZ
85536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(calendar.get(Calendar.YEAR));
85636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
85736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MONTH) + 1));
85836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('-');
85936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.DAY_OF_MONTH)));
86036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append('T');
86136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.HOUR_OF_DAY)));
86236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
86336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.MINUTE)));
86436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(':');
86536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(formatTwo(calendar.get(Calendar.SECOND)));
86636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        sb.append(".000Z");
86736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank        return sb.toString();
86836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank    }
86936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
870c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /**
871c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Note that messages in the deleted database preserve the message's unique id; therefore, we
872c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * can utilize this id to find references to the message.  The only reference situation at this
873c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * point is in the Body table; it is when sending messages via SmartForward and SmartReply
874c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
875c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    private boolean messageReferenced(ContentResolver cr, long id) {
876c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        mBindArgument[0] = Long.toString(id);
877c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // See if this id is referenced in a body
878c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Body.CONTENT_URI, Body.ID_PROJECTION, WHERE_BODY_SOURCE_MESSAGE_KEY,
879c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                mBindArgument, null);
880c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        try {
881c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return c.moveToFirst();
882c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        } finally {
883c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            c.close();
8845acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank        }
885c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
886c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
887c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    /*private*/ /**
888c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * Serialize commands to delete items from the server; as we find items to delete, add their
889c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * id's to the deletedId's array
890c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     *
891c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param s the Serializer we're using to create post data
892c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param deletedIds ids whose deletions are being sent to the server
893c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @param first whether or not this is the first command being sent
894c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @return true if SYNC_COMMANDS hasn't been sent (false otherwise)
895c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     * @throws IOException
896c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank     */
897c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    boolean sendDeletedItems(Serializer s, ArrayList<Long> deletedIds, boolean first)
898c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            throws IOException {
899c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
9005acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank
901ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Find any of our deleted items
902ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
903ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
904ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // We keep track of the list of deleted item id's so that we can remove them from the
905ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // deleted table after the server receives our command
906c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        deletedIds.clear();
907ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
908ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
9095acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                String serverId = c.getString(Message.LIST_SERVER_ID_COLUMN);
9105acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                // Keep going if there's no serverId
9115acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                if (serverId == null) {
9125acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    continue;
913c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                // Also check if this message is referenced elsewhere
914c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                } else if (messageReferenced(cr, c.getLong(Message.CONTENT_ID_COLUMN))) {
915c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    userLog("Postponing deletion of referenced message: ", serverId);
916c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    continue;
9175acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                } else if (first) {
9187c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_COMMANDS);
919ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    first = false;
920ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
921ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Send the command to delete this message
9225acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
923c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                deletedIds.add(c.getLong(Message.LIST_ID_COLUMN));
924ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
925ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
926ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
927ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
928ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
929c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank       return first;
930c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    }
931c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
932c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    @Override
933c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank    public boolean sendLocalChanges(Serializer s) throws IOException {
934c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        ContentResolver cr = mContext.getContentResolver();
935c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
936c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // Never upsync from these folders
937c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (mMailbox.mType == Mailbox.TYPE_DRAFTS || mMailbox.mType == Mailbox.TYPE_OUTBOX) {
938c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank            return false;
939c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        }
940c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
941c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        // This code is split out for unit testing purposes
942c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        boolean firstCommand = sendDeletedItems(s, mDeletedIdList, true);
943c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank
9444f15001bdfd11c79524b4e44d60041967779e763Marc Blank        if (!mFetchRequestList.isEmpty()) {
9454f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // Add FETCH commands for messages that need a body (i.e. we didn't find it during
9464f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // our earlier sync; this happens only in EAS 2.5 where the body couldn't be found
9474f15001bdfd11c79524b4e44d60041967779e763Marc Blank            // after parsing the message's MIME data)
9484f15001bdfd11c79524b4e44d60041967779e763Marc Blank            if (firstCommand) {
9494f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_COMMANDS);
9504f15001bdfd11c79524b4e44d60041967779e763Marc Blank                firstCommand = false;
9514f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
9524f15001bdfd11c79524b4e44d60041967779e763Marc Blank            for (FetchRequest req: mFetchRequestList) {
9534f15001bdfd11c79524b4e44d60041967779e763Marc Blank                s.start(Tags.SYNC_FETCH).data(Tags.SYNC_SERVER_ID, req.serverId).end();
9544f15001bdfd11c79524b4e44d60041967779e763Marc Blank            }
9554f15001bdfd11c79524b4e44d60041967779e763Marc Blank        }
9564f15001bdfd11c79524b4e44d60041967779e763Marc Blank
95791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // Find our trash mailbox, since deletions will have been moved there...
95891e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        long trashMailboxId =
9590a4d05f0d8753c67364f7167e62cea82aef9a81eMarc Blank            Mailbox.findMailboxOfType(mContext, mMailbox.mAccountKey, Mailbox.TYPE_TRASH);
96091e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
961ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        // Do the same now for updated items
962c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        Cursor c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
963ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
96491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
96591e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank        // We keep track of the list of updated item id's as we did above with deleted items
966ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        mUpdatedIdList.clear();
967ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        try {
968ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            while (c.moveToNext()) {
969ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                long id = c.getLong(Message.LIST_ID_COLUMN);
970ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // Say we've handled this update
971ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                mUpdatedIdList.add(id);
972ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // We have the id of the changed item.  But first, we have to find out its current
973ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                // state, since the updated table saves the opriginal state
974ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
975ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        UPDATES_PROJECTION, null, null, null);
976ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                try {
977ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    // If this item no longer exists (shouldn't be possible), just move along
978ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    if (!currentCursor.moveToFirst()) {
979ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                         continue;
980ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
9815acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    // Keep going if there's no serverId
9825acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    String serverId = currentCursor.getString(UPDATES_SERVER_ID_COLUMN);
9835acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    if (serverId == null) {
9845acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                        continue;
9855acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                    }
98691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    // If the message is now in the trash folder, it has been deleted by the user
98791e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    if (currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN) == trashMailboxId) {
988c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                         if (firstCommand) {
9897c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                            s.start(Tags.SYNC_COMMANDS);
990c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                            firstCommand = false;
99191e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        }
99291e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        // Send the command to delete this message
9935acdc9b59e5cd708bf568cded8ddfe3cacb23b73Marc Blank                        s.start(Tags.SYNC_DELETE).data(Tags.SYNC_SERVER_ID, serverId).end();
99491e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                        continue;
99591e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    }
99691e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank
99736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean flagChange = false;
99836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    boolean readChange = false;
99936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
10004471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    long mailbox = currentCursor.getLong(UPDATES_MAILBOX_KEY_COLUMN);
10014471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    if (mailbox != c.getLong(Message.LIST_MAILBOX_KEY_COLUMN)) {
10024471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // The message has moved to another mailbox; add a request for this
10034471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Note: The Sync command doesn't handle moving messages, so we need
10044471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // to handle this as a "request" (similar to meeting response and
10054471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // attachment load)
10064471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mService.addRequest(new MessageMoveRequest(id, mailbox));
10074471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // Regardless of other changes that might be made, we don't want to indicate
10084471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // that this message has been updated until the move request has been
10094471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // handled (without this, a crash between the flag upsync and the move
10104471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        // would cause the move to be lost)
10114471a6960d352242cc65bddf7888cc5335840c74Marc Blank                        mUpdatedIdList.remove(id);
10124471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    }
101336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
101436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // We can only send flag changes to the server in 12.0 or later
10154471a6960d352242cc65bddf7888cc5335840c74Marc Blank                    int flag = 0;
1016d5fadc87ea52aee033afd476369b29b29bfd434fMarc Blank                    if (mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
101736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        flag = currentCursor.getInt(UPDATES_FLAG_COLUMN);
101836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != c.getInt(Message.LIST_FAVORITE_COLUMN)) {
101936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            flagChange = true;
102036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
102136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
102236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
102391e4233059a8b734dd67ffcfa0d08a0d4d8ab17dMarc Blank                    int read = currentCursor.getInt(UPDATES_READ_COLUMN);
102476eb7b252fc410f5f5d4e90ad54d4bde837de0aaMarc Blank                    if (read != c.getInt(Message.LIST_READ_COLUMN)) {
102536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        readChange = true;
102636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
102736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
102836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (!flagChange && !readChange) {
102936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        // In this case, we've got nothing to send to the server
1030ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                        continue;
1031ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
103236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank
1033c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                    if (firstCommand) {
10347c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        s.start(Tags.SYNC_COMMANDS);
1035c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank                        firstCommand = false;
1036ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    }
103736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // Send the change to "read" and "favorite" (flagged)
10387c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                    s.start(Tags.SYNC_CHANGE)
10397c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank                        .data(Tags.SYNC_SERVER_ID, c.getString(Message.LIST_SERVER_ID_COLUMN))
104036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        .start(Tags.SYNC_APPLICATION_DATA);
104136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (readChange) {
104236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        s.data(Tags.EMAIL_READ, Integer.toString(read));
104336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
104436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // "Flag" is a relatively complex concept in EAS 12.0 and above.  It is not only
104536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // the boolean "favorite" that we think of in Gmail, but it also represents a
104636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // follow up action, which can include a subject, start and due dates, and even
104736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // recurrences.  We don't support any of this as yet, but EAS 12.0 and higher
104836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // require that a flag contain a status, a type, and four date fields, two each
104936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    // for start date and end (due) date.
105036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    if (flagChange) {
105136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        if (flag != 0) {
105236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Status 2 = set flag
105336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.start(Tags.EMAIL_FLAG).data(Tags.EMAIL_FLAG_STATUS, "2");
105436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // "FollowUp" is the standard type
105536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.EMAIL_FLAG_TYPE, "FollowUp");
105636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            long now = System.currentTimeMillis();
105736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            Calendar calendar =
105836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                                GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
105936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now);
106036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // Flags are required to have a start date and end date (duplicated)
106136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // First, we'll set the current date/time in GMT as the start time
106236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            String utc = formatDateTime(calendar);
106336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_START_DATE, utc).data(Tags.TASK_UTC_START_DATE, utc);
106436e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            // And then we'll use one week from today for completion date
106536e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            calendar.setTimeInMillis(now + 1*WEEKS);
106636e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            utc = formatDateTime(calendar);
106736e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.data(Tags.TASK_DUE_DATE, utc).data(Tags.TASK_UTC_DUE_DATE, utc);
106836e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.end();
106936e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        } else {
107036e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                            s.tag(Tags.EMAIL_FLAG);
107136e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                        }
107236e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    }
107336e08ce9f808425ed573e182812f3a82ef4d5d45Marc Blank                    s.end().end(); // SYNC_APPLICATION_DATA, SYNC_CHANGE
1074ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                } finally {
1075ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                    currentCursor.close();
1076ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank                }
1077ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            }
1078ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        } finally {
1079ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank            c.close();
1080ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1081ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank
1082c6b98dad8e9002937d5755c61f2d8709807f4d22Marc Blank        if (!firstCommand) {
10837c582a7fb883b3be728f270fbe5277676fe37cf9Marc Blank            s.end(); // SYNC_COMMANDS
1084ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        }
1085ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank        return false;
1086ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank    }
1087ab30d429e0c6069604aead9b5e6845b6b91b6a02Marc Blank}
1088