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