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