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