Switch to using EasSyncBase for mail downsync.

Also terminate any sync upon encountering its first error --
if we see an io, auth, etc. error, there's no point in trying
further folders.

Change-Id: I08b9a3078b3f17774a6f76a49ff87d37686e694e
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index e53363a..a03543d 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -130,8 +130,8 @@
     public static final int RESULT_CLIENT_CERTIFICATE_REQUIRED = -8;
     /** Error code indicating we don't have a protocol version in common with the server. */
     public static final int RESULT_PROTOCOL_VERSION_UNSUPPORTED = -9;
-    /** Error code indicating the account could not be loaded from the provider. */
-    public static final int RESULT_ACCOUNT_ID_INVALID = -10;
+    /** Error code indicating a hard error when initializing the operation. */
+    public static final int RESULT_INITIALIZATION_FAILURE = -10;
     /** Error code indicating a hard data layer error. */
     public static final int RESULT_HARD_DATA_FAILURE = -11;
     /** Error code indicating some other failure. */
@@ -266,11 +266,9 @@
     public int performOperation() {
         // Make sure the account is loaded if it hasn't already been.
         if (!init(false)) {
-            // TODO: Fix this comment and error code, init() can now fail for reasons other than
-            // failing to load the account.
-            LogUtils.i(LOG_TAG, "Failed to load account %d before sending request for operation %s",
+            LogUtils.i(LOG_TAG, "Failed to initialize %d before sending request for operation %s",
                     getAccountId(), getCommand());
-            return RESULT_ACCOUNT_ID_INVALID;
+            return RESULT_INITIALIZATION_FAILURE;
         }
 
         // We handle server redirects by looping, but we need to protect against too much looping.
@@ -721,31 +719,39 @@
                 amAccount.toString(), extras.toString());
     }
 
-    public static void writeResultToSyncResult(final int result, final SyncResult syncResult) {
+    /**
+     * Interpret a result code from an {@link EasOperation} and, if it's an error, write it to
+     * the appropriate field in {@link SyncResult}.
+     * @param result
+     * @param syncResult
+     * @return Whether an error code was written to syncResult.
+     */
+    public static boolean writeResultToSyncResult(final int result, final SyncResult syncResult) {
         switch (result) {
             case RESULT_TOO_MANY_REDIRECTS:
                 syncResult.tooManyRetries = true;
-                break;
+                return true;
             case RESULT_REQUEST_FAILURE:
                 syncResult.stats.numIoExceptions = 1;
-                break;
+                return true;
             case RESULT_FORBIDDEN:
             case RESULT_PROVISIONING_ERROR:
             case RESULT_AUTHENTICATION_ERROR:
             case RESULT_CLIENT_CERTIFICATE_REQUIRED:
                 syncResult.stats.numAuthExceptions = 1;
-                break;
+                return true;
             case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
                 // Only used in validate, so there's never a syncResult to write to here.
                 break;
-            case RESULT_ACCOUNT_ID_INVALID:
+            case RESULT_INITIALIZATION_FAILURE:
             case RESULT_HARD_DATA_FAILURE:
                 syncResult.databaseError = true;
-                break;
+                return true;
             case RESULT_OTHER_FAILURE:
                 // TODO: Is this correct?
                 syncResult.stats.numIoExceptions = 1;
-                break;
+                return true;
         }
+        return false;
     }
 }
diff --git a/src/com/android/exchange/eas/EasSyncBase.java b/src/com/android/exchange/eas/EasSyncBase.java
index dfa5e9b..ede73b5 100644
--- a/src/com/android/exchange/eas/EasSyncBase.java
+++ b/src/com/android/exchange/eas/EasSyncBase.java
@@ -30,7 +30,7 @@
     public static final int RESULT_DONE = 0;
     public static final int RESULT_MORE_AVAILABLE = 1;
 
-    private final boolean mInitialSync;
+    private boolean mInitialSync;
     private final Mailbox mMailbox;
     private EasSyncCollectionTypeBase mCollectionTypeHandler;
 
@@ -39,8 +39,6 @@
     // TODO: Convert to accountId when ready to convert to EasService.
     public EasSyncBase(final Context context, final Account account, final Mailbox mailbox) {
         super(context, account);
-        // TODO: This works for email, but not necessarily for other types.
-        mInitialSync = EmailContent.isInitialSyncKey(getSyncKey());
         mMailbox = mailbox;
     }
 
@@ -82,7 +80,7 @@
         final String syncKey = getSyncKey();
         LogUtils.d(TAG, "Syncing account %d mailbox %d (class %s) with syncKey %s", mAccount.mId,
                 mMailbox.mId, className, syncKey);
-
+        mInitialSync = EmailContent.isInitialSyncKey(syncKey);
         final Serializer s = new Serializer();
         s.start(Tags.SYNC_SYNC);
         s.start(Tags.SYNC_COLLECTIONS);
@@ -120,7 +118,7 @@
     public int performOperation() {
         int result = RESULT_MORE_AVAILABLE;
         mNumWindows = 1;
-        String key = getSyncKey();
+        final String key = getSyncKey();
         while (result == RESULT_MORE_AVAILABLE) {
             result = super.performOperation();
             // TODO: Clear pending request queue.
@@ -156,7 +154,7 @@
             case Mailbox.TYPE_MAIL:
             case Mailbox.TYPE_INBOX:
             case Mailbox.TYPE_DRAFTS:
-//            case Mailbox.TYPE_SENT:
+            case Mailbox.TYPE_SENT:
 //            case Mailbox.TYPE_TRASH:
             case Mailbox.TYPE_JUNK: {
                 return new EasSyncMail();
diff --git a/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java b/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
index 6ff1508..d8e578c 100644
--- a/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
+++ b/src/com/android/exchange/eas/EasSyncCollectionTypeBase.java
@@ -15,9 +15,6 @@
  * These details include:
  * - Forming the request options. Contacts, Calendar, and Mail set this up differently.
  * - Getting the appropriate parser for this collection type.
- * These classes should be stateless, i.e. the distinct subtypes and instances are used simply
- * to have polymorphic behavior for these functions. If member variables are ever added to any
- * of these classes, {@link EasSyncBase} MUST change how it creates these objects.
  */
 public abstract class EasSyncCollectionTypeBase {
 
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index b59822f..05e7a01 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -71,6 +71,7 @@
 import com.android.exchange.eas.EasPing;
 import com.android.exchange.eas.EasSearch;
 import com.android.exchange.eas.EasSync;
+import com.android.exchange.eas.EasSyncBase;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
 
@@ -720,7 +721,6 @@
 
             // Do the bookkeeping for starting a sync, including stopping a ping if necessary.
             mSyncHandlerMap.startSync(account.mId);
-            boolean lastSyncHadError = false;
 
             try {
                 // Perform a FolderSync if necessary.
@@ -760,32 +760,29 @@
                 }
 
                 if (mailboxIds != null) {
-                    long numIoExceptions = 0;
-                    long numAuthExceptions = 0;
                     // Sync the mailbox that was explicitly requested.
                     for (final long mailboxId : mailboxIds) {
-                        final boolean success = syncMailbox(context, cr, acct, account, mailboxId,
+                        final int result = syncMailbox(context, cr, acct, account, mailboxId,
                                 extras, syncResult, null, true);
-                        if (!success) {
-                            lastSyncHadError = true;
-                        }
+                        EasSyncBase.writeResultToSyncResult(result, syncResult);
                         if (hasCallbackMethod) {
-                            final int result;
+                            final int uiResult;
                             if (syncResult.hasError()) {
-                                if (syncResult.stats.numIoExceptions > numIoExceptions) {
-                                    result = UIProvider.LastSyncResult.CONNECTION_ERROR;
-                                    numIoExceptions = syncResult.stats.numIoExceptions;
-                                } else if (syncResult.stats.numAuthExceptions> numAuthExceptions) {
-                                    result = UIProvider.LastSyncResult.AUTH_ERROR;
-                                    numAuthExceptions= syncResult.stats.numAuthExceptions;
+                                if (syncResult.stats.numIoExceptions > 0) {
+                                    uiResult = UIProvider.LastSyncResult.CONNECTION_ERROR;
+                                } else if (syncResult.stats.numAuthExceptions > 0) {
+                                    uiResult = UIProvider.LastSyncResult.AUTH_ERROR;
                                 }  else {
-                                    result = UIProvider.LastSyncResult.INTERNAL_ERROR;
+                                    uiResult = UIProvider.LastSyncResult.INTERNAL_ERROR;
                                 }
                             } else {
-                                result = UIProvider.LastSyncResult.SUCCESS;
+                                uiResult = UIProvider.LastSyncResult.SUCCESS;
                             }
                             EmailServiceStatus.syncMailboxStatus(
-                                    cr, extras, mailboxId,EmailServiceStatus.SUCCESS, 0, result);
+                                    cr, extras, mailboxId,EmailServiceStatus.SUCCESS, 0, uiResult);
+                        }
+                        if (syncResult.hasError()) {
+                            break;
                         }
                     }
                 } else if (!accountOnly && !pushOnly) {
@@ -802,10 +799,11 @@
                         try {
                             final HashSet<String> authsToSync = getAuthsToSync(acct);
                             while (c.moveToNext()) {
-                                boolean success = syncMailbox(context, cr, acct, account,
+                                final int result = syncMailbox(context, cr, acct, account,
                                         c.getLong(0), extras, syncResult, authsToSync, false);
-                                if (!success) {
-                                    lastSyncHadError = true;
+                                EasSyncBase.writeResultToSyncResult(result, syncResult);
+                                if (syncResult.hasError()) {
+                                    break;
                                 }
                             }
                         } finally {
@@ -815,11 +813,13 @@
                 }
             } finally {
                 // Clean up the bookkeeping, including restarting ping if necessary.
-                mSyncHandlerMap.syncComplete(lastSyncHadError, account);
+                mSyncHandlerMap.syncComplete(syncResult.hasError(), account);
 
-                // TODO: It may make sense to have common error handling here. Two possibilities:
-                // 1) performSync return value can signal some useful info.
-                // 2) syncResult can contain useful info.
+                // If any operations had an auth error, notify the user.
+                if (syncResult.stats.numAuthExceptions > 0) {
+                    showAuthNotification(account.mId, account.mEmailAddress);
+                }
+
                 LogUtils.d(TAG, "onPerformSync: finished");
             }
         }
@@ -841,24 +841,24 @@
             mailbox.update(context, cv);
         }
 
-        private boolean syncMailbox(final Context context, final ContentResolver cr,
+        private int syncMailbox(final Context context, final ContentResolver cr,
                 final android.accounts.Account acct, final Account account, final long mailboxId,
                 final Bundle extras, final SyncResult syncResult, final HashSet<String> authsToSync,
                 final boolean isMailboxSync) {
             final Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
             if (mailbox == null) {
-                return false;
+                return EasSyncBase.RESULT_HARD_DATA_FAILURE;
             }
 
             if (mailbox.mAccountKey != account.mId) {
                 LogUtils.e(TAG, "Mailbox does not match account: %s, %s", acct.toString(),
                         extras.toString());
-                return false;
+                return EasSyncBase.RESULT_HARD_DATA_FAILURE;
             }
             if (authsToSync != null && !authsToSync.contains(Mailbox.getAuthority(mailbox.mType))) {
                 // We are asking for an account sync, but this mailbox type is not configured for
                 // sync. Do NOT treat this as a sync error for ping backoff purposes.
-                return true;
+                return EasSyncBase.RESULT_DONE;
             }
 
             if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
@@ -869,36 +869,51 @@
                 // that we won't sync even if the user attempts to force a sync from the UI.
                 // Do NOT treat as a sync error for ping backoff purposes.
                 LogUtils.d(TAG, "Skipping sync of DRAFTS folder");
-                return true;
+                return EasSyncBase.RESULT_DONE;
             }
-            final boolean success;
+
             // Non-mailbox syncs are whole account syncs initiated by the AccountManager and are
             // treated as background syncs.
             // TODO: Push will be treated as "user" syncs, and probably should be background.
             final ContentValues cv = new ContentValues(2);
             updateMailbox(context, mailbox, cv, isMailboxSync ?
                     EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND);
-            if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
-                int result = syncOutbox(context, cr, account, mailbox);
-                // TODO: in some cases we might want to abort the sync completely.
-                success = true;
-            } else if(mailbox.isSyncable()) {
-                final EasSyncHandler syncHandler = EasSyncHandler.getEasSyncHandler(context, cr,
-                        acct, account, mailbox, extras, syncResult);
-                if (syncHandler != null) {
-                    success = syncHandler.performSync(syncResult);
-                } else {
-                    success = false;
+            try {
+                if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
+                    return syncOutbox(context, cr, account, mailbox);
                 }
-            } else {
-                success = false;
-            }
-            updateMailbox(context, mailbox, cv, EmailContent.SYNC_STATUS_NONE);
 
-            if (syncResult.stats.numAuthExceptions > 0) {
-                showAuthNotification(account.mId, account.mEmailAddress);
+                if(mailbox.isSyncable()) {
+                    // TODO: This conditional logic is temporary until EasSyncHandler is obsolete.
+                    if (mailbox.mType == Mailbox.TYPE_INBOX || mailbox.mType == Mailbox.TYPE_MAIL ||
+                            mailbox.mType == Mailbox.TYPE_SENT) {
+                        final EasSyncBase operation = new EasSyncBase(context, account, mailbox);
+                        return operation.performOperation();
+                    } else {
+                        // TODO: This branch goes away after all conversions are done.
+                        final EasSyncHandler syncHandler = EasSyncHandler.getEasSyncHandler(context,
+                                cr, acct, account, mailbox, extras, syncResult);
+                        if (syncHandler != null) {
+                            if (!syncHandler.performSync(syncResult)) {
+                                // This is ass-backwards, but it's a hack until this code goes away.
+                                if (syncResult.stats.numIoExceptions > 0) {
+                                    return EasSyncBase.RESULT_REQUEST_FAILURE;
+                                }
+                                if (syncResult.stats.numAuthExceptions > 0) {
+                                    return EasSyncBase.RESULT_AUTHENTICATION_ERROR;
+                                }
+                                if (syncResult.stats.numParseExceptions > 0) {
+                                    return EasSyncBase.RESULT_OTHER_FAILURE;
+                                }
+                            }
+                        }
+                    }
+                }
+            } finally {
+                updateMailbox(context, mailbox, cv, EmailContent.SYNC_STATUS_NONE);
             }
-            return success;
+
+            return EasSyncBase.RESULT_DONE;
         }
     }