| /* |
| * Copyright (C) 2008-2009 Marc Blank |
| * Licensed to The Android Open Source Project. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.exchange.adapter; |
| |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.OperationApplicationException; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent; |
| import com.android.emailcommon.provider.EmailContent.MailboxColumns; |
| import com.android.emailcommon.provider.Mailbox; |
| import com.android.exchange.CommandStatusException; |
| import com.android.exchange.CommandStatusException.CommandStatus; |
| import com.android.exchange.Eas; |
| import com.android.mail.utils.LogUtils; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| |
| /** |
| * Base class for the Email and PIM sync parsers |
| * Handles the basic flow of syncKeys, looping to get more data, handling errors, etc. |
| * Each subclass must implement a handful of methods that relate specifically to the data type |
| * |
| */ |
| public abstract class AbstractSyncParser extends Parser { |
| private static final String TAG = Eas.LOG_TAG; |
| |
| protected Mailbox mMailbox; |
| protected Account mAccount; |
| protected Context mContext; |
| protected ContentResolver mContentResolver; |
| |
| private boolean mLooping; |
| |
| public AbstractSyncParser(final Context context, final ContentResolver resolver, |
| final InputStream in, final Mailbox mailbox, final Account account) throws IOException { |
| super(in); |
| init(context, resolver, mailbox, account); |
| } |
| |
| public AbstractSyncParser(InputStream in, AbstractSyncAdapter adapter) throws IOException { |
| super(in); |
| init(adapter); |
| } |
| |
| public AbstractSyncParser(Parser p, AbstractSyncAdapter adapter) throws IOException { |
| super(p); |
| init(adapter); |
| } |
| |
| public AbstractSyncParser(final Parser p, final Context context, final ContentResolver resolver, |
| final Mailbox mailbox, final Account account) throws IOException { |
| super(p); |
| init(context, resolver, mailbox, account); |
| } |
| |
| private void init(final AbstractSyncAdapter adapter) { |
| init(adapter.mContext, adapter.mContext.getContentResolver(), adapter.mMailbox, |
| adapter.mAccount); |
| } |
| |
| private void init(final Context context, final ContentResolver resolver, final Mailbox mailbox, |
| final Account account) { |
| mContext = context; |
| mContentResolver = resolver; |
| mMailbox = mailbox; |
| mAccount = account; |
| } |
| |
| /** |
| * Read, parse, and act on incoming commands from the Exchange server |
| * @throws IOException if the connection is broken |
| * @throws CommandStatusException |
| */ |
| public abstract void commandsParser() throws IOException, CommandStatusException; |
| |
| /** |
| * Read, parse, and act on server responses |
| * @throws IOException |
| */ |
| public abstract void responsesParser() throws IOException; |
| |
| /** |
| * Commit any changes found during parsing |
| * @throws IOException |
| */ |
| public abstract void commit() throws IOException, RemoteException, |
| OperationApplicationException; |
| |
| public boolean isLooping() { |
| return mLooping; |
| } |
| |
| /** |
| * Skip through tags until we reach the specified end tag |
| * @param endTag the tag we end with |
| * @throws IOException |
| */ |
| public void skipParser(int endTag) throws IOException { |
| while (nextTag(endTag) != END) { |
| skipTag(); |
| } |
| } |
| |
| /** |
| * Loop through the top-level structure coming from the Exchange server |
| * Sync keys and the more available flag are handled here, whereas specific data parsing |
| * is handled by abstract methods implemented for each data class (e.g. Email, Contacts, etc.) |
| * @throws CommandStatusException |
| */ |
| @Override |
| public boolean parse() throws IOException, CommandStatusException { |
| int status; |
| boolean moreAvailable = false; |
| boolean newSyncKey = false; |
| mLooping = false; |
| // If we're not at the top of the xml tree, throw an exception |
| if (nextTag(START_DOCUMENT) != Tags.SYNC_SYNC) { |
| throw new EasParserException(); |
| } |
| |
| boolean mailboxUpdated = false; |
| ContentValues cv = new ContentValues(); |
| |
| // Loop here through the remaining xml |
| while (nextTag(START_DOCUMENT) != END_DOCUMENT) { |
| if (tag == Tags.SYNC_COLLECTION || tag == Tags.SYNC_COLLECTIONS) { |
| // Ignore these tags, since we've only got one collection syncing in this loop |
| } else if (tag == Tags.SYNC_STATUS) { |
| // Status = 1 is success; everything else is a failure |
| status = getValueInt(); |
| if (status != 1) { |
| if (status == 3 || CommandStatus.isBadSyncKey(status)) { |
| // Must delete all of the data and start over with syncKey of "0" |
| mMailbox.mSyncKey = "0"; |
| newSyncKey = true; |
| wipe(); |
| // Indicate there's more so that we'll start syncing again |
| moreAvailable = true; |
| } else if (status == 16 || status == 5) { |
| // Status 16 indicates a transient server error (indeterminate state) |
| // Status 5 indicates "server error"; this tends to loop for a while so |
| // throwing IOException will at least provide backoff behavior |
| throw new IOException(); |
| } else if (status == 8 || status == 12) { |
| // Status 8 is Bad; it means the server doesn't recognize the serverId it |
| // sent us. 12 means that we're being asked to refresh the folder list. |
| // We'll do that with 8 also... |
| // TODO: Improve this -- probably best to do this synchronously and then |
| // immediately retry the current sync. |
| final Bundle extras = new Bundle(1); |
| extras.putBoolean(Mailbox.SYNC_EXTRA_ACCOUNT_ONLY, true); |
| ContentResolver.requestSync(new android.accounts.Account( |
| mAccount.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE), |
| EmailContent.AUTHORITY, extras); |
| // We don't have any provision for telling the user "wait a minute while |
| // we sync folders"... |
| throw new IOException(); |
| } else if (status == 7) { |
| // TODO: Fix this. The handling here used to be pretty bogus, and it's not |
| // obvious that simply forcing another resync makes sense here. |
| moreAvailable = true; |
| } else { |
| LogUtils.e(LogUtils.TAG, "Sync: Unknown status: " + status); |
| // Access, provisioning, transient, etc. |
| throw new CommandStatusException(status); |
| } |
| } |
| } else if (tag == Tags.SYNC_COMMANDS) { |
| commandsParser(); |
| } else if (tag == Tags.SYNC_RESPONSES) { |
| responsesParser(); |
| } else if (tag == Tags.SYNC_MORE_AVAILABLE) { |
| moreAvailable = true; |
| } else if (tag == Tags.SYNC_SYNC_KEY) { |
| if (mMailbox.mSyncKey.equals("0")) { |
| moreAvailable = true; |
| } |
| String newKey = getValue(); |
| userLog("Parsed key for ", mMailbox.mDisplayName, ": ", newKey); |
| if (!newKey.equals(mMailbox.mSyncKey)) { |
| mMailbox.mSyncKey = newKey; |
| cv.put(MailboxColumns.SYNC_KEY, newKey); |
| mailboxUpdated = true; |
| newSyncKey = true; |
| } |
| } else { |
| skipTag(); |
| } |
| } |
| |
| // If we don't have a new sync key, ignore moreAvailable (or we'll loop) |
| if (moreAvailable && !newSyncKey) { |
| LogUtils.e(TAG, "Looping detected"); |
| mLooping = true; |
| } |
| |
| // Commit any changes |
| try { |
| commit(); |
| if (mailboxUpdated) { |
| mMailbox.update(mContext, cv); |
| } |
| } catch (RemoteException e) { |
| LogUtils.e(TAG, "Failed to commit changes", e); |
| } catch (OperationApplicationException e) { |
| LogUtils.e(TAG, "Failed to commit changes", e); |
| } |
| // Let the caller know that there's more to do |
| if (moreAvailable) { |
| userLog("MoreAvailable"); |
| } |
| return moreAvailable; |
| } |
| |
| abstract protected void wipe(); |
| |
| void userLog(String ...strings) { |
| // TODO: Convert to other logging types? |
| //mService.userLog(strings); |
| } |
| |
| void userLog(String string, int num, String string2) { |
| // TODO: Convert to other logging types? |
| //mService.userLog(string, num, string2); |
| } |
| } |