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 
     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