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