• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.exchange.adapter;
19 
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Context;
23 
24 import com.android.emailcommon.provider.Account;
25 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
26 import com.android.emailcommon.provider.Mailbox;
27 import com.android.exchange.CommandStatusException;
28 import com.android.exchange.CommandStatusException.CommandStatus;
29 import com.android.exchange.EasSyncService;
30 import com.android.exchange.ExchangeService;
31 
32 import java.io.IOException;
33 import java.io.InputStream;
34 
35 /**
36  * Base class for the Email and PIM sync parsers
37  * Handles the basic flow of syncKeys, looping to get more data, handling errors, etc.
38  * Each subclass must implement a handful of methods that relate specifically to the data type
39  *
40  */
41 public abstract class AbstractSyncParser extends Parser {
42 
43     protected EasSyncService mService;
44     protected Mailbox mMailbox;
45     protected Account mAccount;
46     protected Context mContext;
47     protected ContentResolver mContentResolver;
48     protected AbstractSyncAdapter mAdapter;
49 
50     private boolean mLooping;
51 
AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter)52     public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException {
53         super(in);
54         init(adapter);
55     }
56 
AbstractSyncParser(Parser p, AbstractSyncAdapter adapter)57     public AbstractSyncParser(Parser p, AbstractSyncAdapter adapter) throws IOException {
58         super(p);
59         init(adapter);
60     }
61 
init(AbstractSyncAdapter adapter)62     private void init(AbstractSyncAdapter adapter) {
63         mAdapter = adapter;
64         mService = adapter.mService;
65         mContext = mService.mContext;
66         mContentResolver = mContext.getContentResolver();
67         mMailbox = mService.mMailbox;
68         mAccount = mService.mAccount;
69     }
70 
71     /**
72      * Read, parse, and act on incoming commands from the Exchange server
73      * @throws IOException if the connection is broken
74      * @throws CommandStatusException
75      */
commandsParser()76     public abstract void commandsParser() throws IOException, CommandStatusException;
77 
78     /**
79      * Read, parse, and act on server responses
80      * @throws IOException
81      */
responsesParser()82     public abstract void responsesParser() throws IOException;
83 
84     /**
85      * Commit any changes found during parsing
86      * @throws IOException
87      */
commit()88     public abstract void commit() throws IOException;
89 
isLooping()90     public boolean isLooping() {
91         return mLooping;
92     }
93 
94     /**
95      * Skip through tags until we reach the specified end tag
96      * @param endTag the tag we end with
97      * @throws IOException
98      */
skipParser(int endTag)99     public void skipParser(int endTag) throws IOException {
100         while (nextTag(endTag) != END) {
101             skipTag();
102         }
103     }
104 
105     /**
106      * Loop through the top-level structure coming from the Exchange server
107      * Sync keys and the more available flag are handled here, whereas specific data parsing
108      * is handled by abstract methods implemented for each data class (e.g. Email, Contacts, etc.)
109      * @throws CommandStatusException
110      */
111     @Override
parse()112     public boolean parse() throws IOException, CommandStatusException {
113         int status;
114         boolean moreAvailable = false;
115         boolean newSyncKey = false;
116         int interval = mMailbox.mSyncInterval;
117         mLooping = false;
118         // If we're not at the top of the xml tree, throw an exception
119         if (nextTag(START_DOCUMENT) != Tags.SYNC_SYNC) {
120             throw new EasParserException();
121         }
122 
123         boolean mailboxUpdated = false;
124         ContentValues cv = new ContentValues();
125 
126         // Loop here through the remaining xml
127         while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
128             if (tag == Tags.SYNC_COLLECTION || tag == Tags.SYNC_COLLECTIONS) {
129                 // Ignore these tags, since we've only got one collection syncing in this loop
130             } else if (tag == Tags.SYNC_STATUS) {
131                 // Status = 1 is success; everything else is a failure
132                 status = getValueInt();
133                 if (status != 1) {
134                     mService.errorLog("Sync failed: " + CommandStatus.toString(status));
135                     if (status == 3 || CommandStatus.isBadSyncKey(status)) {
136                         // Must delete all of the data and start over with syncKey of "0"
137                         mAdapter.setSyncKey("0", false);
138                         // Make this a push box through the first sync
139                         // TODO Make frequency conditional on user settings!
140                         mMailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_PUSH;
141                         mService.errorLog("Bad sync key; RESET and delete data");
142                         mAdapter.wipe();
143                         // Indicate there's more so that we'll start syncing again
144                         moreAvailable = true;
145                     } else if (status == 16 || status == 5) {
146                         // Status 16 indicates a transient server error (indeterminate state)
147                         // Status 5 indicates "server error"; this tends to loop for a while so
148                         // throwing IOException will at least provide backoff behavior
149                         throw new IOException();
150                     } else if (status == 8 || status == 12) {
151                         // Status 8 is Bad; it means the server doesn't recognize the serverId it
152                         // sent us.  12 means that we're being asked to refresh the folder list.
153                         // We'll do that with 8 also...
154                         ExchangeService.reloadFolderList(mContext, mAccount.mId, true);
155                         // We don't have any provision for telling the user "wait a minute while
156                         // we sync folders"...
157                         throw new IOException();
158                     } else if (status == 7) {
159                         mService.mUpsyncFailed = true;
160                         moreAvailable = true;
161                     } else {
162                         // Access, provisioning, transient, etc.
163                         throw new CommandStatusException(status);
164                     }
165                 }
166             } else if (tag == Tags.SYNC_COMMANDS) {
167                 commandsParser();
168             } else if (tag == Tags.SYNC_RESPONSES) {
169                 responsesParser();
170             } else if (tag == Tags.SYNC_MORE_AVAILABLE) {
171                 moreAvailable = true;
172             } else if (tag == Tags.SYNC_SYNC_KEY) {
173                 if (mAdapter.getSyncKey().equals("0")) {
174                     moreAvailable = true;
175                 }
176                 String newKey = getValue();
177                 userLog("Parsed key for ", mMailbox.mDisplayName, ": ", newKey);
178                 if (!newKey.equals(mMailbox.mSyncKey)) {
179                     mAdapter.setSyncKey(newKey, true);
180                     cv.put(MailboxColumns.SYNC_KEY, newKey);
181                     mailboxUpdated = true;
182                     newSyncKey = true;
183                 }
184                 // If we were pushing (i.e. auto-start), now we'll become ping-triggered
185                 if (mMailbox.mSyncInterval == Mailbox.CHECK_INTERVAL_PUSH) {
186                     mMailbox.mSyncInterval = Mailbox.CHECK_INTERVAL_PING;
187                 }
188            } else {
189                 skipTag();
190            }
191         }
192 
193         // If we don't have a new sync key, ignore moreAvailable (or we'll loop)
194         if (moreAvailable && !newSyncKey) {
195             mLooping = true;
196         }
197 
198         // Commit any changes
199         commit();
200 
201         boolean abortSyncs = false;
202 
203         // If the sync interval has changed, we need to save it
204         if (mMailbox.mSyncInterval != interval) {
205             cv.put(MailboxColumns.SYNC_INTERVAL, mMailbox.mSyncInterval);
206             mailboxUpdated = true;
207         // If there are changes, and we were bounced from push/ping, try again
208         } else if (mService.mChangeCount > 0 &&
209                 mAccount.mSyncInterval == Account.CHECK_INTERVAL_PUSH &&
210                 mMailbox.mSyncInterval > 0) {
211             userLog("Changes found to ping loop mailbox ", mMailbox.mDisplayName, ": will ping.");
212             cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PING);
213             mailboxUpdated = true;
214             abortSyncs = true;
215         }
216 
217         if (mailboxUpdated) {
218              synchronized (mService.getSynchronizer()) {
219                 if (!mService.isStopped()) {
220                      mMailbox.update(mContext, cv);
221                 }
222             }
223         }
224 
225         if (abortSyncs) {
226             userLog("Aborting account syncs due to mailbox change to ping...");
227             ExchangeService.stopAccountSyncs(mAccount.mId);
228         }
229 
230         // Let the caller know that there's more to do
231         if (moreAvailable) {
232             userLog("MoreAvailable");
233         }
234         return moreAvailable;
235     }
236 
userLog(String ....strings)237     void userLog(String ...strings) {
238         mService.userLog(strings);
239     }
240 
userLog(String string, int num, String string2)241     void userLog(String string, int num, String string2) {
242         mService.userLog(string, num, string2);
243     }
244 }
245