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 LogUtils.e(LogUtils.TAG, "Sync: Unknown status: " + status); 172 // Access, provisioning, transient, etc. 173 throw new CommandStatusException(status); 174 } 175 } 176 } else if (tag == Tags.SYNC_COMMANDS) { 177 commandsParser(); 178 } else if (tag == Tags.SYNC_RESPONSES) { 179 responsesParser(); 180 } else if (tag == Tags.SYNC_MORE_AVAILABLE) { 181 moreAvailable = true; 182 } else if (tag == Tags.SYNC_SYNC_KEY) { 183 if (mMailbox.mSyncKey.equals("0")) { 184 moreAvailable = true; 185 } 186 String newKey = getValue(); 187 userLog("Parsed key for ", mMailbox.mDisplayName, ": ", newKey); 188 if (!newKey.equals(mMailbox.mSyncKey)) { 189 mMailbox.mSyncKey = newKey; 190 cv.put(MailboxColumns.SYNC_KEY, newKey); 191 mailboxUpdated = true; 192 newSyncKey = true; 193 } 194 } else { 195 skipTag(); 196 } 197 } 198 199 // If we don't have a new sync key, ignore moreAvailable (or we'll loop) 200 if (moreAvailable && !newSyncKey) { 201 LogUtils.e(TAG, "Looping detected"); 202 mLooping = true; 203 } 204 205 // Commit any changes 206 try { 207 commit(); 208 if (mailboxUpdated) { 209 mMailbox.update(mContext, cv); 210 } 211 } catch (RemoteException e) { 212 LogUtils.e(TAG, "Failed to commit changes", e); 213 } catch (OperationApplicationException e) { 214 LogUtils.e(TAG, "Failed to commit changes", e); 215 } 216 // Let the caller know that there's more to do 217 if (moreAvailable) { 218 userLog("MoreAvailable"); 219 } 220 return moreAvailable; 221 } 222 223 abstract protected void wipe(); 224 225 void userLog(String ...strings) { 226 // TODO: Convert to other logging types? 227 //mService.userLog(strings); 228 } 229 230 void userLog(String string, int num, String string2) { 231 // TODO: Convert to other logging types? 232 //mService.userLog(string, num, string2); 233 } 234 } 235