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