1328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu/*
2328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * Copyright (C) 2013 The Android Open Source Project
3328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu *
4328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * Licensed under the Apache License, Version 2.0 (the "License");
5328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * you may not use this file except in compliance with the License.
6328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * You may obtain a copy of the License at
7328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu *
8328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu *      http://www.apache.org/licenses/LICENSE-2.0
9328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu *
10328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * Unless required by applicable law or agreed to in writing, software
11328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * distributed under the License is distributed on an "AS IS" BASIS,
12328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * See the License for the specific language governing permissions and
14328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * limitations under the License.
15328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu */
16328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
17328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hupackage com.android.exchange.eas;
18328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
19c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport android.content.ContentResolver;
20c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport android.content.ContentUris;
21328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport android.content.Context;
22c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport android.database.Cursor;
23c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport android.support.v4.util.LongSparseArray;
2494599580f738c4700174be250ecf7392768a432bTony Mantlerimport android.text.TextUtils;
25328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport android.text.format.DateUtils;
26328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
27328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.emailcommon.provider.Account;
28c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport com.android.emailcommon.provider.EmailContent;
29328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.emailcommon.provider.Mailbox;
30c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport com.android.emailcommon.provider.MessageStateChange;
31c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport com.android.exchange.CommandStatusException;
32328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.exchange.Eas;
33328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.exchange.EasResponse;
34c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport com.android.exchange.adapter.EmailSyncParser;
35c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport com.android.exchange.adapter.Parser;
36328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.exchange.adapter.Serializer;
37328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport com.android.exchange.adapter.Tags;
3894599580f738c4700174be250ecf7392768a432bTony Mantlerimport com.android.mail.utils.LogUtils;
39328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
40328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport org.apache.http.HttpEntity;
41328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
42328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Huimport java.io.IOException;
43c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.Calendar;
44c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.GregorianCalendar;
45c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.List;
46c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.Locale;
47c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.Map;
48c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Huimport java.util.TimeZone;
49328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
50328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu/**
51328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu * Performs an Exchange Sync operation for one {@link Mailbox}.
52c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu * TODO: For now, only handles upsync.
53c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu * TODO: Handle multiple folders in one request. Not sure if parser can handle it yet.
54328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu */
55328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hupublic class EasSync extends EasOperation {
56328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
578c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    /** Result code indicating that the mailbox for an upsync is no longer present. */
588c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    public final static int RESULT_NO_MAILBOX = 0;
598c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    public final static int RESULT_OK = 1;
608c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu
61c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    // TODO: When we handle downsync, this will become relevant.
62c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private boolean mInitialSync;
63328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
64c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    // State for the mailbox we're currently syncing.
65c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private long mMailboxId;
66c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private String mMailboxServerId;
67c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private String mMailboxSyncKey;
68c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private List<MessageStateChange> mStateChanges;
69c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private Map<String, Integer> mMessageUpdateStatus;
70c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
71c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    public EasSync(final Context context, final Account account) {
72328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        super(context, account);
73328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        mInitialSync = false;
74328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
75328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
76c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private long getMessageId(final String serverId) {
77c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        // TODO: Improve this.
78c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        for (final MessageStateChange change : mStateChanges) {
79c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            if (change.getServerId().equals(serverId)) {
80c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                return change.getMessageId();
81c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
82c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
83c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        return EmailContent.Message.NO_MESSAGE;
84c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    }
85c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
86c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private void handleMessageUpdateStatus(final Map<String, Integer> messageStatus,
87c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final long[][] messageIds, final int[] counts) {
88c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        for (final Map.Entry<String, Integer> entry : messageStatus.entrySet()) {
89c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final String serverId = entry.getKey();
90c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final int status = entry.getValue();
91c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final int index;
92c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            if (EmailSyncParser.shouldRetry(status)) {
93c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                index = 1;
94c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            } else {
95c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                index = 0;
96c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
97c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final long messageId = getMessageId(serverId);
98c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            if (messageId != EmailContent.Message.NO_MESSAGE) {
99c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                messageIds[index][counts[index]] = messageId;
100c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                ++counts[index];
101c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
102c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
103c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    }
104c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
105c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    /**
1068c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu     * @return Number of messages successfully synced, or a negative response code from
1078c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu     *         {@link EasOperation} if we encountered any errors.
108c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu     */
1098c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    public final int upsync() {
1106c4254903d2f42836c5b74a934e54441ccee6dbeYu Ping Hu        final List<MessageStateChange> changes = MessageStateChange.getChanges(mContext,
1116c4254903d2f42836c5b74a934e54441ccee6dbeYu Ping Hu                getAccountId(), getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE);
112c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        if (changes == null) {
113c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            return 0;
114c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
115c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final LongSparseArray<List<MessageStateChange>> allData =
116c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                MessageStateChange.convertToChangesMap(changes);
117c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        if (allData == null) {
118c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            return 0;
119c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
120c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
121c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final long[][] messageIds = new long[2][changes.size()];
122c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final int[] counts = new int[2];
1238c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu        int result = 0;
124c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
125c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        for (int i = 0; i < allData.size(); ++i) {
126c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            mMailboxId = allData.keyAt(i);
127c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            mStateChanges = allData.valueAt(i);
1288c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            boolean retryMailbox = true;
1298c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            // If we've already encountered a fatal error, don't even try to upsync subsequent
1308c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            // mailboxes.
1318c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            if (result >= 0) {
1328c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                final Cursor mailboxCursor = mContext.getContentResolver().query(
1338c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                        ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId),
1348c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                        Mailbox.ProjectionSyncData.PROJECTION, null, null, null);
1358c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                if (mailboxCursor != null) {
1368c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                    try {
1378c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                        if (mailboxCursor.moveToFirst()) {
1388c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                            mMailboxServerId = mailboxCursor.getString(
1398c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    Mailbox.ProjectionSyncData.COLUMN_SERVER_ID);
1408c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                            mMailboxSyncKey = mailboxCursor.getString(
1418c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    Mailbox.ProjectionSyncData.COLUMN_SYNC_KEY);
1428c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                            if (TextUtils.isEmpty(mMailboxSyncKey) || mMailboxSyncKey.equals("0")) {
1438c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                // For some reason we can get here without a valid mailbox sync key
1448c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                // b/10797675
1458c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                // TODO: figure out why and clean this up
1468c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                LogUtils.d(LOG_TAG,
1478c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        "Tried to sync mailbox %d with invalid mailbox sync key",
1488c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        mMailboxId);
1498c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                            } else {
1508c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                result = performOperation();
1518c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                if (result >= 0) {
1528c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    // Our request gave us back a legitimate answer; this is the
1538c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    // only case in which we don't retry this mailbox.
1548c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    retryMailbox = false;
1558c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    if (result == RESULT_OK) {
1568c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        handleMessageUpdateStatus(mMessageUpdateStatus, messageIds,
1578c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                                counts);
1588c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    } else if (result == RESULT_NO_MAILBOX) {
1598c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        // A retry here is pointless -- the message's mailbox (and
1608c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        // therefore the message) is gone, so mark as success so
1618c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        // that these entries get wiped from the change list.
1628c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        for (final MessageStateChange msc : mStateChanges) {
1638c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                            messageIds[0][counts[0]] = msc.getMessageId();
1648c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                            ++counts[0];
1658c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        }
1668c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    } else {
1678c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                        LogUtils.wtf(LOG_TAG, "Unrecognized result code: %d",
1688c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                                result);
1698c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                    }
1708c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                                }
17194599580f738c4700174be250ecf7392768a432bTony Mantler                            }
172c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                        }
1738c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                    } finally {
1748c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                        mailboxCursor.close();
175c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    }
1768c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                }
1778c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            }
1788c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            if (retryMailbox) {
1798c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                for (final MessageStateChange msc : mStateChanges) {
1808c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                    messageIds[1][counts[1]] = msc.getMessageId();
1818c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu                    ++counts[1];
182c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                }
183c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
184c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
185c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
186c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final ContentResolver cr = mContext.getContentResolver();
187c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        MessageStateChange.upsyncSuccessful(cr, messageIds[0], counts[0]);
188c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        MessageStateChange.upsyncRetry(cr, messageIds[1], counts[1]);
189c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu
1908c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu        if (result < 0) {
1918c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            return result;
1928c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu        }
1938c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu        return counts[0];
194c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    }
195328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
196328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    @Override
197328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    protected String getCommand() {
198328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        return "Sync";
199328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
200328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
201328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    @Override
202328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    protected HttpEntity getRequestEntity() throws IOException {
2036f0cf29be849d3a95a9168cb6fa09de12635e818Yu Ping Hu        final Serializer s = new Serializer();
2046f0cf29be849d3a95a9168cb6fa09de12635e818Yu Ping Hu        s.start(Tags.SYNC_SYNC);
2056f0cf29be849d3a95a9168cb6fa09de12635e818Yu Ping Hu        s.start(Tags.SYNC_COLLECTIONS);
206c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        addOneCollectionToRequest(s, Mailbox.TYPE_MAIL, mMailboxServerId, mMailboxSyncKey,
207c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                mStateChanges);
2086f0cf29be849d3a95a9168cb6fa09de12635e818Yu Ping Hu        s.end().end().done();
2096f0cf29be849d3a95a9168cb6fa09de12635e818Yu Ping Hu        return makeEntity(s);
210328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
211328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
212328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    @Override
2138c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu    protected int handleResponse(final EasResponse response)
2147f0b7b0d66b40ec3004fbc329dbd34a801608b92Yu Ping Hu            throws IOException, CommandStatusException {
215c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
216c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        if (mailbox == null) {
2178c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu            return RESULT_NO_MAILBOX;
218c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
219c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final EmailSyncParser parser = new EmailSyncParser(mContext, mContext.getContentResolver(),
2206c4254903d2f42836c5b74a934e54441ccee6dbeYu Ping Hu                response.getInputStream(), mailbox, mAccount);
221c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        try {
222c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            parser.parse();
223c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            mMessageUpdateStatus = parser.getMessageStatuses();
224c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        } catch (final Parser.EmptyStreamException e) {
225c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            // This indicates a compressed response which was empty, which is OK.
226c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
2278c95506ad96f5990d5e4d4ca86b05ec95dd4b1d6Yu Ping Hu        return RESULT_OK;
228328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
229328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
230328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    @Override
231328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    protected long getTimeout() {
232328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        if (mInitialSync) {
233328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu            return 120 * DateUtils.SECOND_IN_MILLIS;
234328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        }
235328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        return super.getTimeout();
236328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
237328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
238c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    /**
239c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu     * Create date/time in RFC8601 format.  Oddly enough, for calendar date/time, Microsoft uses
240c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu     * a different format that excludes the punctuation (this is why I'm not putting this in a
241c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu     * parent class)
242c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu     */
243c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu    private static String formatDateTime(final Calendar calendar) {
244c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        final StringBuilder sb = new StringBuilder();
245c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        //YYYY-MM-DDTHH:MM:SS.MSSZ
246c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(calendar.get(Calendar.YEAR));
247c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append('-');
248c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(String.format(Locale.US, "%02d", calendar.get(Calendar.MONTH) + 1));
249c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append('-');
250c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(String.format(Locale.US, "%02d", calendar.get(Calendar.DAY_OF_MONTH)));
251c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append('T');
252c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(String.format(Locale.US, "%02d", calendar.get(Calendar.HOUR_OF_DAY)));
253c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(':');
254c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(String.format(Locale.US, "%02d", calendar.get(Calendar.MINUTE)));
255c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(':');
256c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(String.format(Locale.US, "%02d", calendar.get(Calendar.SECOND)));
257c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        sb.append(".000Z");
258c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        return sb.toString();
259328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
260328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
261ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler    private void addOneCollectionToRequest(final Serializer s, final int collectionType,
262c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final String mailboxServerId, final String mailboxSyncKey,
263c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final List<MessageStateChange> stateChanges) throws IOException {
264328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu
265c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        s.start(Tags.SYNC_COLLECTION);
266c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
267c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            s.data(Tags.SYNC_CLASS, Eas.getFolderClass(collectionType));
268c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        }
269c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        s.data(Tags.SYNC_SYNC_KEY, mailboxSyncKey);
270c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        s.data(Tags.SYNC_COLLECTION_ID, mailboxServerId);
271ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
272ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler            // Exchange 2003 doesn't understand the concept of setting this flag to false. The
273ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler            // documentation indicates that its presence alone, with no value, requests a two-way
274ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler            // sync.
275ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler            // TODO: handle downsync here so we don't need this at all
276ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler            s.data(Tags.SYNC_GET_CHANGES, "0");
277ca8ae4efb836bee643396f1fde52e0c87a8c40c1Tony Mantler        }
278c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        s.start(Tags.SYNC_COMMANDS);
279c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        for (final MessageStateChange change : stateChanges) {
280c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            s.start(Tags.SYNC_CHANGE);
281c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            s.data(Tags.SYNC_SERVER_ID, change.getServerId());
282c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            s.start(Tags.SYNC_APPLICATION_DATA);
283c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final int newFlagRead = change.getNewFlagRead();
284c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            if (newFlagRead != MessageStateChange.VALUE_UNCHANGED) {
285c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                s.data(Tags.EMAIL_READ, Integer.toString(newFlagRead));
286c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
287c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            final int newFlagFavorite = change.getNewFlagFavorite();
288c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            if (newFlagFavorite != MessageStateChange.VALUE_UNCHANGED) {
289c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // "Flag" is a relatively complex concept in EAS 12.0 and above.  It is not only
290c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // the boolean "favorite" that we think of in Gmail, but it also represents a
291c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // follow up action, which can include a subject, start and due dates, and even
292c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // recurrences.  We don't support any of this as yet, but EAS 12.0 and higher
293c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // require that a flag contain a status, a type, and four date fields, two each
294c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                // for start date and end (due) date.
295c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                if (newFlagFavorite != 0) {
296c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    // Status 2 = set flag
297c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.start(Tags.EMAIL_FLAG).data(Tags.EMAIL_FLAG_STATUS, "2");
298c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    // "FollowUp" is the standard type
299c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.data(Tags.EMAIL_FLAG_TYPE, "FollowUp");
300c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    final long now = System.currentTimeMillis();
301c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    final Calendar calendar =
302c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                            GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
303c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    calendar.setTimeInMillis(now);
304c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    // Flags are required to have a start date and end date (duplicated)
305c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    // First, we'll set the current date/time in GMT as the start time
306c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    String utc = formatDateTime(calendar);
307c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.data(Tags.TASK_START_DATE, utc).data(Tags.TASK_UTC_START_DATE, utc);
308c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    // And then we'll use one week from today for completion date
309c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    calendar.setTimeInMillis(now + DateUtils.WEEK_IN_MILLIS);
310c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    utc = formatDateTime(calendar);
311c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.data(Tags.TASK_DUE_DATE, utc).data(Tags.TASK_UTC_DUE_DATE, utc);
312c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.end();
313c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                } else {
314c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                    s.tag(Tags.EMAIL_FLAG);
315c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu                }
316c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            }
317c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu            s.end().end();  // SYNC_APPLICATION_DATA, SYNC_CHANGE
318328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu        }
319c640541ba09b4ecd88f4fa9603dc2d9d474e7996Yu Ping Hu        s.end().end();  // SYNC_COMMANDS, SYNC_COLLECTION
320328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu    }
321328ca0d959f7e729e96f19538c8f3af8fe782a09Yu Ping Hu}
322