1/*
2 * Copyright (C) 2008-2009 Marc Blank
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.exchange.adapter;
19
20import com.android.email.provider.EmailContent.Account;
21import com.android.email.provider.EmailContent.Mailbox;
22import com.android.email.provider.EmailContent.MailboxColumns;
23import com.android.exchange.EasSyncService;
24import com.android.exchange.SyncManager;
25
26import android.content.ContentResolver;
27import android.content.ContentValues;
28import android.content.Context;
29
30import java.io.IOException;
31import java.io.InputStream;
32
33/**
34 * Base class for the Email and PIM sync parsers
35 * Handles the basic flow of syncKeys, looping to get more data, handling errors, etc.
36 * Each subclass must implement a handful of methods that relate specifically to the data type
37 *
38 */
39public abstract class AbstractSyncParser extends Parser {
40
41    protected EasSyncService mService;
42    protected Mailbox mMailbox;
43    protected Account mAccount;
44    protected Context mContext;
45    protected ContentResolver mContentResolver;
46    protected AbstractSyncAdapter mAdapter;
47
48    private boolean mLooping;
49
50    public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException {
51        super(in);
52        mAdapter = adapter;
53        mService = adapter.mService;
54        mContext = mService.mContext;
55        mContentResolver = mContext.getContentResolver();
56        mMailbox = mService.mMailbox;
57        mAccount = mService.mAccount;
58    }
59
60    /**
61     * Read, parse, and act on incoming commands from the Exchange server
62     * @throws IOException if the connection is broken
63     */
64    public abstract void commandsParser() throws IOException;
65
66    /**
67     * Read, parse, and act on server responses
68     * @throws IOException
69     */
70    public abstract void responsesParser() throws IOException;
71
72    /**
73     * Commit any changes found during parsing
74     * @throws IOException
75     */
76    public abstract void commit() throws IOException;
77
78    /**
79     * Delete all records of this class in this account
80     */
81    public abstract void wipe();
82
83    public boolean isLooping() {
84        return mLooping;
85    }
86
87    /**
88     * Loop through the top-level structure coming from the Exchange server
89     * Sync keys and the more available flag are handled here, whereas specific data parsing
90     * is handled by abstract methods implemented for each data class (e.g. Email, Contacts, etc.)
91     */
92    @Override
93    public boolean parse() throws IOException {
94        int status;
95        boolean moreAvailable = false;
96        boolean newSyncKey = false;
97        int interval = mMailbox.mSyncInterval;
98        mLooping = false;
99        // If we're not at the top of the xml tree, throw an exception
100        if (nextTag(START_DOCUMENT) != Tags.SYNC_SYNC) {
101            throw new EasParserException();
102        }
103
104        boolean mailboxUpdated = false;
105        ContentValues cv = new ContentValues();
106
107        // Loop here through the remaining xml
108        while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
109            if (tag == Tags.SYNC_COLLECTION || tag == Tags.SYNC_COLLECTIONS) {
110                // Ignore these tags, since we've only got one collection syncing in this loop
111            } else if (tag == Tags.SYNC_STATUS) {
112                // Status = 1 is success; everything else is a failure
113                status = getValueInt();
114                if (status != 1) {
115                    mService.errorLog("Sync failed: " + status);
116                    // Status = 3 means invalid sync key
117                    if (status == 3) {
118                        // Must delete all of the data and start over with syncKey of "0"
119                        mAdapter.setSyncKey("0", false);
120                        // Make this a push box through the first sync
121                        // TODO Make frequency conditional on user settings!
122                        mMailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_PUSH;
123                        mService.errorLog("Bad sync key; RESET and delete data");
124                        wipe();
125                        // Indicate there's more so that we'll start syncing again
126                        moreAvailable = true;
127                    } else if (status == 8) {
128                        // This is Bad; it means the server doesn't recognize the serverId it
129                        // sent us.  What's needed is a refresh of the folder list.
130                        SyncManager.reloadFolderList(mContext, mAccount.mId, true);
131                    }
132                    // TODO Look at other error codes and consider what's to be done
133                }
134            } else if (tag == Tags.SYNC_COMMANDS) {
135                commandsParser();
136            } else if (tag == Tags.SYNC_RESPONSES) {
137                responsesParser();
138            } else if (tag == Tags.SYNC_MORE_AVAILABLE) {
139                moreAvailable = true;
140            } else if (tag == Tags.SYNC_SYNC_KEY) {
141                if (mAdapter.getSyncKey().equals("0")) {
142                    moreAvailable = true;
143                }
144                String newKey = getValue();
145                userLog("Parsed key for ", mMailbox.mDisplayName, ": ", newKey);
146                if (!newKey.equals(mMailbox.mSyncKey)) {
147                    mAdapter.setSyncKey(newKey, true);
148                    cv.put(MailboxColumns.SYNC_KEY, newKey);
149                    mailboxUpdated = true;
150                    newSyncKey = true;
151                }
152                // If we were pushing (i.e. auto-start), now we'll become ping-triggered
153                if (mMailbox.mSyncInterval == Mailbox.CHECK_INTERVAL_PUSH) {
154                    mMailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_PING;
155                }
156           } else {
157                skipTag();
158           }
159        }
160
161        // If we don't have a new sync key, ignore moreAvailable (or we'll loop)
162        if (moreAvailable && !newSyncKey) {
163            mLooping = true;
164        }
165
166        // Commit any changes
167        commit();
168
169        boolean abortSyncs = false;
170
171        // If the sync interval has changed, we need to save it
172        if (mMailbox.mSyncInterval != interval) {
173            cv.put(MailboxColumns.SYNC_INTERVAL, mMailbox.mSyncInterval);
174            mailboxUpdated = true;
175        // If there are changes, and we were bounced from push/ping, try again
176        } else if (mService.mChangeCount > 0 &&
177                mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH &&
178                mMailbox.mSyncInterval > 0) {
179            userLog("Changes found to ping loop mailbox ", mMailbox.mDisplayName, ": will ping.");
180            cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PING);
181            mailboxUpdated = true;
182            abortSyncs = true;
183        }
184
185        if (mailboxUpdated) {
186             synchronized (mService.getSynchronizer()) {
187                if (!mService.isStopped()) {
188                     mMailbox.update(mContext, cv);
189                }
190            }
191        }
192
193        if (abortSyncs) {
194            userLog("Aborting account syncs due to mailbox change to ping...");
195            SyncManager.stopAccountSyncs(mAccount.mId);
196        }
197
198        // Let the caller know that there's more to do
199        userLog("Returning moreAvailable = " + moreAvailable);
200        return moreAvailable;
201    }
202
203    void userLog(String ...strings) {
204        mService.userLog(strings);
205    }
206
207    void userLog(String string, int num, String string2) {
208        mService.userLog(string, num, string2);
209    }
210}
211