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 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 61 public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException { 62 super(in); 63 init(adapter); 64 } 65 66 public AbstractSyncParser(Parser p, AbstractSyncAdapter adapter) throws IOException { 67 super(p); 68 init(adapter); 69 } 70 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 77 private void init(final AbstractSyncAdapter adapter) { 78 init(adapter.mContext, adapter.mContext.getContentResolver(), adapter.mMailbox, 79 adapter.mAccount); 80 } 81 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 */ 95 public abstract void commandsParser() throws IOException, CommandStatusException; 96 97 /** 98 * Read, parse, and act on server responses 99 * @throws IOException 100 */ 101 public abstract void responsesParser() throws IOException; 102 103 /** 104 * Commit any changes found during parsing 105 * @throws IOException 106 */ 107 public abstract void commit() throws IOException, RemoteException, 108 OperationApplicationException; 109 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 */ 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 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 236 abstract protected void wipe(); 237 238 void userLog(String ...strings) { 239 // TODO: Convert to other logging types? 240 //mService.userLog(strings); 241 } 242 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