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