Update IEmailService

This is not yet finished, but it's needed to fix push syncs on
exchange, which are currently broken.
Now the actual work of syncing up and down is done in EasService.
EmailSyncAdapterService just gets the onPerformSync() call and
has the EasService do the work. This makes way for eventually
moving the sync adapter and the ping synchronizer out of exchange
and into email.

Change-Id: Ic5608887230393560612c9fa45992e4413317bfd
(cherry picked from commit 61ebb38e67421fd122c81f046bf11778b61a2113)
diff --git a/src/com/android/exchange/eas/EasFolderSync.java b/src/com/android/exchange/eas/EasFolderSync.java
index 34da843..f9853b8 100644
--- a/src/com/android/exchange/eas/EasFolderSync.java
+++ b/src/com/android/exchange/eas/EasFolderSync.java
@@ -116,22 +116,6 @@
     }
 
     /**
-     * Perform a folder sync.
-     * TODO: Remove this function when transition to EasService is complete.
-     * @return A result code, either from above or from the base class.
-     */
-    public int doFolderSync() {
-        if (mStatusOnly) {
-            return RESULT_WRONG_OPERATION;
-        }
-        LogUtils.d(LOG_TAG, "Performing sync for account %d", getAccountId());
-        // This intentionally calls super.performOperation -- calling our performOperation
-        // will simply end up calling super.performOperation anyway. This is part of the transition
-        // to EasService and will go away when this function is deleted.
-        return super.performOperation();
-    }
-
-    /**
      * Helper function for {@link #performOperation} -- do some initial checks and, if they pass,
      * perform a folder sync to verify that we can. This sets {@link #mValidationResult} as a side
      * effect which holds the result details needed by the UI.
@@ -171,16 +155,6 @@
         return result;
     }
 
-    /**
-     * Perform account validation.
-     * TODO: Remove this function when transition to EasService is complete.
-     * @return The response {@link Bundle} expected by the RPC.
-     */
-    public Bundle doValidate() {
-        validate();
-        return mValidationResult;
-    }
-
     @Override
     protected String getCommand() {
         return "FolderSync";
@@ -240,7 +214,7 @@
             case RESULT_TOO_MANY_REDIRECTS:
                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
                 break;
-            case RESULT_REQUEST_FAILURE:
+            case RESULT_NETWORK_PROBLEM:
                 messagingExceptionCode = MessagingException.IOERROR;
                 break;
             case RESULT_FORBIDDEN:
diff --git a/src/com/android/exchange/eas/EasFullSyncOperation.java b/src/com/android/exchange/eas/EasFullSyncOperation.java
new file mode 100644
index 0000000..79b07a8
--- /dev/null
+++ b/src/com/android/exchange/eas/EasFullSyncOperation.java
@@ -0,0 +1,348 @@
+package com.android.exchange.eas;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
+
+import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Message;
+import com.android.emailcommon.provider.Mailbox;
+import com.android.emailcommon.service.EmailServiceStatus;
+import com.android.emailcommon.utility.Utility;
+import com.android.exchange.CommandStatusException;
+import com.android.exchange.Eas;
+import com.android.exchange.EasResponse;
+import com.android.exchange.service.EasService;
+import com.android.mail.providers.UIProvider;
+import com.android.mail.utils.LogUtils;
+
+import org.apache.http.HttpEntity;
+
+import java.io.IOException;
+import java.util.Set;
+
+public class EasFullSyncOperation extends EasOperation {
+    private final static String TAG = LogUtils.TAG;
+
+    private final static int RESULT_SUCCESS = 0;
+    private final static int RESULT_SECURITY_HOLD = -100;
+
+    public static final int SEND_FAILED = 1;
+    public static final String MAILBOX_KEY_AND_NOT_SEND_FAILED =
+            EmailContent.MessageColumns.MAILBOX_KEY + "=? and (" +
+                    EmailContent.SyncColumns.SERVER_ID + " is null or " +
+                    EmailContent.SyncColumns.SERVER_ID + "!=" + SEND_FAILED + ')';
+    /**
+     * The content authorities that can be synced for EAS accounts. Initialization must wait until
+     * after we have a chance to call {@link EmailContent#init} (and, for future content types,
+     * possibly other initializations) because that's how we can know what the email authority is.
+     */
+    private static String[] AUTHORITIES_TO_SYNC;
+
+    static {
+        // Statically initialize the authorities we'll sync.
+        AUTHORITIES_TO_SYNC = new String[] {
+                EmailContent.AUTHORITY,
+                CalendarContract.AUTHORITY,
+                ContactsContract.AUTHORITY
+        };
+    }
+
+    final Bundle mSyncExtras;
+    Set<String> mAuthsToSync;
+
+    public EasFullSyncOperation(final Context context, final long accountId,
+                                final Bundle syncExtras) {
+        super(context, accountId);
+        mSyncExtras = syncExtras;
+    }
+
+    @Override
+    protected String getCommand() {
+        // This is really a container operation, its performOperation() actually just creates and
+        // performs a bunch of other operations. It doesn't actually do any of its own
+        // requests.
+        // TODO: This is kind of ugly, maybe we need a simpler base class for EasOperation that
+        // does not assume that it will perform a single network operation.
+        LogUtils.e(TAG, "unexpected call to EasFullSyncOperation.getCommand");
+        return null;
+    }
+
+    @Override
+    protected HttpEntity getRequestEntity() throws IOException {
+        // This is really a container operation, its performOperation() actually just creates and
+        // performs a bunch of other operations. It doesn't actually do any of its own
+        // requests.
+        LogUtils.e(TAG, "unexpected call to EasFullSyncOperation.getRequestEntity");
+        return null;
+    }
+
+    @Override
+    protected int handleResponse(final EasResponse response)
+            throws IOException, CommandStatusException {
+        // This is really a container operation, its performOperation() actually just creates and
+        // performs a bunch of other operations. It doesn't actually do any of its own
+        // requests.
+        LogUtils.e(TAG, "unexpected call to EasFullSyncOperation.handleResponse");
+        return RESULT_SUCCESS;
+    }
+
+    @Override
+    public int performOperation() {
+        // Make sure the account is loaded if it hasn't already been.
+        if (!init(false)) {
+            LogUtils.i(LOG_TAG, "Failed to initialize %d before operation EasFullSyncOperation",
+                    getAccountId());
+            return RESULT_INITIALIZATION_FAILURE;
+        }
+        final android.accounts.Account amAccount = new android.accounts.Account(
+                mAccount.mEmailAddress, Eas.EXCHANGE_ACCOUNT_MANAGER_TYPE);
+        mAuthsToSync = EasService.getAuthoritiesToSync(amAccount, AUTHORITIES_TO_SYNC);
+
+        // Figure out what we want to sync, based on the extras and our account sync status.
+        final boolean isInitialSync = EmailContent.isInitialSyncKey(mAccount.mSyncKey);
+        final long[] mailboxIds = Mailbox.getMailboxIdsFromBundle(mSyncExtras);
+        final int mailboxType = mSyncExtras.getInt(Mailbox.SYNC_EXTRA_MAILBOX_TYPE,
+                Mailbox.TYPE_NONE);
+
+        // Push only means this sync request should only refresh the ping (either because
+        // settings changed, or we need to restart it for some reason).
+        final boolean pushOnly = Mailbox.isPushOnlyExtras(mSyncExtras);
+        // Account only means just do a FolderSync.
+        final boolean accountOnly = Mailbox.isAccountOnlyExtras(mSyncExtras);
+        final boolean hasCallbackMethod =
+                mSyncExtras.containsKey(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_METHOD);
+        // A "full sync" means that we didn't request a more specific type of sync.
+        // In this case we sync the folder list and all syncable folders.
+        final boolean isFullSync = (!pushOnly && !accountOnly && mailboxIds == null &&
+                mailboxType == Mailbox.TYPE_NONE);
+        // A FolderSync is necessary for full sync, initial sync, and account only sync.
+        final boolean isFolderSync = (isFullSync || isInitialSync || accountOnly);
+
+        int result;
+
+        // Now we will use a bunch of other EasOperations to actually do the sync. Note that
+        // since we have overridden performOperation, this EasOperation does not have the
+        // normal handling of errors and retrying that is built in. The handling of errors and
+        // retries is done in each individual operation.
+
+        // Perform a FolderSync if necessary.
+        // TODO: We permit FolderSync even during security hold, because it's necessary to
+        // resolve some holds. Ideally we would only do it for the holds that require it.
+        if (isFolderSync) {
+            final EasFolderSync folderSync = new EasFolderSync(mContext, mAccount);
+            result = folderSync.performOperation();
+            if (isFatal(result)) {
+                // This is a failure, abort the sync.
+                LogUtils.i(TAG, "Fatal result %d on folderSync", result);
+                return result;
+            }
+        }
+
+        // Do not permit further syncs if we're on security hold.
+        if ((mAccount.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
+            return RESULT_SECURITY_HOLD;
+        }
+
+        if (!isInitialSync) {
+            EasMoveItems moveOp = new EasMoveItems(mContext, mAccount);
+            result = moveOp.upsyncMovedMessages();
+            if (isFatal(result)) {
+                // This is a failure, abort the sync.
+                LogUtils.i(TAG, "Fatal result %d on MoveItems", result);
+                return result;
+            }
+
+            final EasSync upsync = new EasSync(mContext, mAccount);
+            result = upsync.upsync();
+            if (isFatal(result)) {
+                // This is a failure, abort the sync.
+                LogUtils.i(TAG, "Fatal result %d on upsync", result);
+                return result;
+            }
+        }
+
+        if (mailboxIds != null) {
+            // Sync the mailbox that was explicitly requested.
+            for (final long mailboxId : mailboxIds) {
+                // TODO: how to tell if a sync was user requested or not.
+                // We should be able to check the MANUAL flag in the sync extras, but right now
+                // all of our syncs are manual. This is because Calender and Contacts sync adapters
+                // actually just forward the request through the EmailSyncAdapterService.
+                // When this changes we can determine for certain whether or not this is a manual
+                // sync.
+                result = syncMailbox(mailboxId, hasCallbackMethod, true);
+                if (isFatal(result)) {
+                    // This is a failure, abort the sync.
+                    LogUtils.i(TAG, "Fatal result %d on syncMailbox", result);
+                    return result;
+                }
+            }
+        } else if (!accountOnly && !pushOnly) {
+           // We have to sync multiple folders.
+            final Cursor c;
+            if (isFullSync) {
+                // Full account sync includes all mailboxes that participate in system sync.
+                c = Mailbox.getMailboxIdsForSync(mContext.getContentResolver(), mAccount.mId);
+            } else {
+                // Type-filtered sync should only get the mailboxes of a specific type.
+                c = Mailbox.getMailboxIdsForSyncByType(mContext.getContentResolver(),
+                        mAccount.mId, mailboxType);
+            }
+            if (c != null) {
+                try {
+                    while (c.moveToNext()) {
+                        result = syncMailbox(c.getLong(0), hasCallbackMethod, false);
+                        if (isFatal(result)) {
+                            // This is a failure, abort the sync.
+                            LogUtils.i(TAG, "Fatal result %d on syncMailbox", result);
+                            return result;
+                        }
+                    }
+                } finally {
+                    c.close();
+                }
+            }
+        }
+
+        return RESULT_SUCCESS;
+    }
+
+    private int syncMailbox(final long folderId, final boolean hasCallbackMethod,
+                            final boolean isUserSync) {
+        final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, folderId);
+        if (mailbox == null) {
+            LogUtils.d(TAG, "Could not load folder %d", folderId);
+            return EasSyncBase.RESULT_HARD_DATA_FAILURE;
+        }
+
+        if (mailbox.mAccountKey != mAccount.mId) {
+            LogUtils.e(TAG, "Mailbox does not match account: mailbox %s, %s", mAccount.toString(),
+                    mSyncExtras);
+            return EasSyncBase.RESULT_HARD_DATA_FAILURE;
+        }
+
+        if (mAuthsToSync != null && !mAuthsToSync.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 EasSyncBase.RESULT_DONE;
+        }
+
+        if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
+            // TODO: Because we don't have bidirectional sync working, trying to downsync
+            // the drafts folder is confusing. b/11158759
+            // For now, just disable all syncing of DRAFTS type folders.
+            // Automatic syncing should always be disabled, but we also stop it here to ensure
+            // 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 EmailServiceStatus.SUCCESS;
+        }
+
+        int result = 0;
+        // Non-mailbox syncs are whole account syncs initiated by the AccountManager and are
+        // treated as background syncs.
+        if (mailbox.mType == Mailbox.TYPE_OUTBOX || mailbox.isSyncable()) {
+            final ContentValues cv = new ContentValues(2);
+            updateMailbox(mailbox, cv, isUserSync ?
+                    EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND);
+            try {
+                if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
+                    return syncOutbox(mailbox.mId);
+                }
+                if (hasCallbackMethod) {
+                    EmailServiceStatus.syncMailboxStatus(mContext.getContentResolver(), mSyncExtras,
+                            mailbox.mId, EmailServiceStatus.IN_PROGRESS, 0,
+                            UIProvider.LastSyncResult.SUCCESS);
+                }
+                final EasSyncBase operation = new EasSyncBase(mContext, mAccount, mailbox);
+                LogUtils.d(TAG, "IEmailService.syncMailbox account %d", mAccount.mId);
+                result = operation.performOperation();
+            } finally {
+                updateMailbox(mailbox, cv, EmailContent.SYNC_STATUS_NONE);
+                if (hasCallbackMethod) {
+                    EmailServiceStatus.syncMailboxStatus(mContext.getContentResolver(), mSyncExtras,
+                            mailbox.mId, EmailServiceStatus.SUCCESS, 0,
+                            EasOperation.translateSyncResultToUiResult(result));
+                }
+            }
+        } else {
+            // This mailbox is not syncable.
+            LogUtils.d(TAG, "Skipping sync of non syncable folder");
+        }
+
+        return result;
+    }
+
+    private int syncOutbox(final long mailboxId) {
+        LogUtils.d(TAG, "syncOutbox %d", mAccount.mId);
+        // Because syncing the outbox uses a single EasOperation for every message, we don't
+        // want to use doOperation(). That would stop and restart the ping between each operation,
+        // which is wasteful if we have several messages to send.
+        final Cursor c = mContext.getContentResolver().query(EmailContent.Message.CONTENT_URI,
+                EmailContent.Message.CONTENT_PROJECTION, MAILBOX_KEY_AND_NOT_SEND_FAILED,
+                new String[] {Long.toString(mailboxId)}, null);
+        try {
+            // Loop through the messages, sending each one
+            while (c.moveToNext()) {
+                final Message message = new Message();
+                message.restore(c);
+                if (Utility.hasUnloadedAttachments(mContext, message.mId)) {
+                    // We'll just have to wait on this...
+                    // TODO: We should make sure that this attachment is queued for download here.
+                    continue;
+                }
+
+                // TODO: Fix -- how do we want to signal to UI that we started syncing?
+                // Note the entire callback mechanism here needs improving.
+                //sendMessageStatus(message.mId, null, EmailServiceStatus.IN_PROGRESS, 0);
+
+                EasOperation op = new EasOutboxSync(mContext, mAccount, message, true);
+
+                int result = op.performOperation();
+                if (result == EasOutboxSync.RESULT_ITEM_NOT_FOUND) {
+                    // This can happen if we are using smartReply, and the message we are referring
+                    // to has disappeared from the server. Try again with smartReply disabled.
+                    op = new EasOutboxSync(mContext, mAccount, message, false);
+                    result = op.performOperation();
+                }
+                // If we got some connection error or other fatal error, terminate the sync.
+                // RESULT_NON_FATAL_ERROR
+                if (result != EasOutboxSync.RESULT_OK &&
+                        result != EasOutboxSync.RESULT_NON_FATAL_ERROR &&
+                        result > EasOutboxSync.RESULT_OP_SPECIFIC_ERROR_RESULT) {
+                    LogUtils.w(TAG, "Aborting outbox sync for error %d", result);
+                    return result;
+                }
+            }
+        } finally {
+            // TODO: Some sort of sendMessageStatus() is needed here.
+            c.close();
+        }
+
+        return EasOutboxSync.RESULT_OK;
+    }
+
+
+    /**
+     * Update the mailbox's sync status with the provider and, if we're finished with the sync,
+     * write the last sync time as well.
+     * @param mailbox The mailbox whose sync status to update.
+     * @param cv A {@link ContentValues} object to use for updating the provider.
+     * @param syncStatus The status for the current sync.
+     */
+    private void updateMailbox(final Mailbox mailbox, final ContentValues cv,
+                               final int syncStatus) {
+        cv.put(Mailbox.UI_SYNC_STATUS, syncStatus);
+        if (syncStatus == EmailContent.SYNC_STATUS_NONE) {
+            cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
+        }
+        mailbox.update(mContext, cv);
+    }
+}
diff --git a/src/com/android/exchange/eas/EasLoadAttachment.java b/src/com/android/exchange/eas/EasLoadAttachment.java
index 71e524f..9527e51 100644
--- a/src/com/android/exchange/eas/EasLoadAttachment.java
+++ b/src/com/android/exchange/eas/EasLoadAttachment.java
@@ -280,7 +280,7 @@
         // the errors that are being returned to the caller of performOperation().
         if (response.isEmpty()) {
             LogUtils.e(LOG_TAG, "Error, empty response.");
-            return RESULT_REQUEST_FAILURE;
+            return RESULT_NETWORK_PROBLEM;
         }
 
         // This is a 2 step process.
@@ -291,7 +291,7 @@
             tmpFile = File.createTempFile("eas_", "tmp", mContext.getCacheDir());
         } catch (final IOException e) {
             LogUtils.e(LOG_TAG, "Could not open temp file: %s", e.getMessage());
-            return RESULT_REQUEST_FAILURE;
+            return RESULT_NETWORK_PROBLEM;
         }
 
         try {
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index 259a540..0950bff 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -114,6 +114,9 @@
      * -200, etc...).
      */
 
+    /** Minimum value for any non failure result. There may be multiple different non-failure
+     * results, if so they should all be greater than or equal to this value. */
+    public static final int RESULT_MIN_OK_RESULT = 0;
     /** Error code indicating the operation was cancelled via {@link #abort}. */
     public static final int RESULT_ABORT = -1;
     /** Error code indicating the operation was cancelled via {@link #restart}. */
@@ -121,7 +124,7 @@
     /** Error code indicating the Exchange servers redirected too many times. */
     public static final int RESULT_TOO_MANY_REDIRECTS = -3;
     /** Error code indicating the request failed due to a network problem. */
-    public static final int RESULT_REQUEST_FAILURE = -4;
+    public static final int RESULT_NETWORK_PROBLEM = -4;
     /** Error code indicating a 403 (forbidden) error. */
     public static final int RESULT_FORBIDDEN = -5;
     /** Error code indicating an unresolved provisioning error. */
@@ -166,6 +169,10 @@
         mConnection = connection;
     }
 
+    public static boolean isFatal(int result) {
+        return result < RESULT_MIN_OK_RESULT;
+    }
+
     /**
      * Constructor which defers loading of account and connection info.
      * @param context
@@ -329,12 +336,16 @@
                     message = "(no message)";
                 }
                 LogUtils.i(LOG_TAG, "IOException while sending request: %s", message);
-                return RESULT_REQUEST_FAILURE;
+                return RESULT_NETWORK_PROBLEM;
             } catch (final CertificateException e) {
                 LogUtils.i(LOG_TAG, "CertificateException while sending request: %s",
                         e.getMessage());
                 return RESULT_CLIENT_CERTIFICATE_REQUIRED;
             } catch (final MessageInvalidException e) {
+                // This indicates that there is something wrong with the message locally, and it
+                // cannot be sent. We don't want to return success, because that's misleading,
+                // but on the other hand, we don't want to abort the sync, because that would
+                // prevent other messages from being sent.
                 LogUtils.d(LOG_TAG, "Exception sending request %s", e.getMessage());
                 return RESULT_NON_FATAL_ERROR;
             } catch (final IllegalStateException e) {
@@ -354,7 +365,7 @@
                         responseResult = handleResponse(response);
                     } catch (final IOException e) {
                         LogUtils.e(LOG_TAG, e, "Exception while handling response");
-                        return RESULT_REQUEST_FAILURE;
+                        return RESULT_NETWORK_PROBLEM;
                     } catch (final CommandStatusException e) {
                         // For some operations (notably Sync & FolderSync), errors are signaled in
                         // the payload of the response. These will have a HTTP 200 response, and the
@@ -378,7 +389,7 @@
 
                 // Non-negative results indicate success. Return immediately and bypass the error
                 // handling.
-                if (result >= 0) {
+                if (result >= EasOperation.RESULT_MIN_OK_RESULT) {
                     return result;
                 }
 
@@ -770,47 +781,11 @@
                 amAccount.toString(), extras.toString());
     }
 
-    /**
-     * 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;
-                return true;
-            case RESULT_REQUEST_FAILURE:
-                syncResult.stats.numIoExceptions = 1;
-                return true;
-            case RESULT_FORBIDDEN:
-            case RESULT_PROVISIONING_ERROR:
-            case RESULT_AUTHENTICATION_ERROR:
-            case RESULT_CLIENT_CERTIFICATE_REQUIRED:
-                syncResult.stats.numAuthExceptions = 1;
-                return true;
-            case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
-                // Only used in validate, so there's never a syncResult to write to here.
-                break;
-            case RESULT_INITIALIZATION_FAILURE:
-            case RESULT_HARD_DATA_FAILURE:
-                syncResult.databaseError = true;
-                return true;
-            case RESULT_OTHER_FAILURE:
-                // TODO: Is this correct?
-                syncResult.stats.numIoExceptions = 1;
-                return true;
-        }
-        return false;
-    }
-
     public static int translateSyncResultToUiResult(final int result) {
         switch (result) {
               case RESULT_TOO_MANY_REDIRECTS:
                 return UIProvider.LastSyncResult.INTERNAL_ERROR;
-            case RESULT_REQUEST_FAILURE:
+            case RESULT_NETWORK_PROBLEM:
                 return UIProvider.LastSyncResult.CONNECTION_ERROR;
             case RESULT_FORBIDDEN:
             case RESULT_PROVISIONING_ERROR:
diff --git a/src/com/android/exchange/eas/EasPing.java b/src/com/android/exchange/eas/EasPing.java
index 3135397..5893ead 100644
--- a/src/com/android/exchange/eas/EasPing.java
+++ b/src/com/android/exchange/eas/EasPing.java
@@ -104,7 +104,7 @@
         final int result = performOperation();
         if (result == RESULT_RESTART) {
             return PingParser.STATUS_EXPIRED;
-        } else  if (result == RESULT_REQUEST_FAILURE) {
+        } else  if (result == RESULT_NETWORK_PROBLEM) {
             final long timeoutDuration = SystemClock.elapsedRealtime() - startTime;
             LogUtils.d(TAG, "doPing request failure, timed out after %d millis", timeoutDuration);
             decreasePingDuration();
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index 5bab199..791266e 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -18,6 +18,7 @@
 
 import android.app.Service;
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
@@ -30,23 +31,29 @@
 
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Message;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.provider.Mailbox;
 import com.android.emailcommon.service.EmailServiceProxy;
+import com.android.emailcommon.service.EmailServiceStatus;
 import com.android.emailcommon.service.IEmailService;
 import com.android.emailcommon.service.IEmailServiceCallback;
 import com.android.emailcommon.service.SearchParams;
 import com.android.emailcommon.service.ServiceProxy;
+import com.android.emailcommon.utility.Utility;
 import com.android.exchange.Eas;
 import com.android.exchange.eas.EasAutoDiscover;
 import com.android.exchange.eas.EasFolderSync;
+import com.android.exchange.eas.EasFullSyncOperation;
 import com.android.exchange.eas.EasLoadAttachment;
 import com.android.exchange.eas.EasOperation;
+import com.android.exchange.eas.EasOutboxSync;
 import com.android.exchange.eas.EasSearch;
 import com.android.exchange.eas.EasSearchGal;
 import com.android.exchange.eas.EasSendMeetingResponse;
 import com.android.exchange.eas.EasSyncBase;
 import com.android.exchange.provider.GalResult;
+import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
 
 import java.util.HashSet;
@@ -78,11 +85,6 @@
      */
     private final IEmailService.Stub mBinder = new IEmailService.Stub() {
         @Override
-        public void sendMail(final long accountId) {
-            LogUtils.d(TAG, "IEmailService.sendMail: %d", accountId);
-        }
-
-        @Override
         public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
                 final long attachmentId, final boolean background) {
             LogUtils.d(TAG, "IEmailService.loadAttachment: %d", attachmentId);
@@ -97,26 +99,14 @@
             doOperation(operation, "IEmailService.updateFolderList");
         }
 
-        @Override
-        public void syncFolders(final long accountId, final boolean updateFolderList,
-                         final long[] folders) {
-            final Account account = Account.restoreAccountWithId(EasService.this, accountId);
-            for (final long folderId : folders) {
-                // TODO: Performance would be improved if we could do multiple folders in
-                // a single operation.
-                final Mailbox mailbox = Mailbox.restoreMailboxWithId(EasService.this, folderId);
-                EasOperation op = new EasSyncBase(EasService.this, account, mailbox);
-            }
+        public void sendMail(final long accountId) {
+            // TODO: We should get rid of sendMail, and this is done in sync.
+            LogUtils.wtf(TAG, "unexpected call to EasService.sendMail");
         }
 
-        @Override
-        public void syncMailboxType(final long accountId, final boolean updateFolderList,
-                         final int mailboxType) {
-            final Account account = Account.restoreAccountWithId(EasService.this, accountId);
-            // TODO: What if there are multiple mailboxes of this type?
-            final Mailbox mailbox = Mailbox.restoreMailboxOfType(EasService.this, accountId,
-                    mailboxType);
-            EasOperation op = new EasSyncBase(EasService.this, account, mailbox);
+        public int sync(final long accountId, Bundle syncExtras) {
+            EasFullSyncOperation op = new EasFullSyncOperation(EasService.this, accountId, syncExtras);
+            return convertToEmailServiceStatus(doOperation(op, "IEmailService.sync"));
         }
 
         @Override
@@ -315,10 +305,8 @@
     }
 
     public int doOperation(final EasOperation operation, final String loggingName) {
-        final long accountId = operation.getAccountId();
-        final Account account = Account.restoreAccountWithId(this, accountId);
-        LogUtils.d(TAG, "%s: %d", loggingName, accountId);
-        mSynchronizer.syncStart(accountId);
+        LogUtils.d(TAG, "%s: %d", loggingName, operation.getAccountId());
+        mSynchronizer.syncStart(operation.getAccountId());
         // TODO: Do we need a wakelock here? For RPC coming from sync adapters, no -- the SA
         // already has one. But for others, maybe? Not sure what's guaranteed for AIDL calls.
         // If we add a wakelock (or anything else for that matter) here, must remember to undo
@@ -327,7 +315,7 @@
         try {
             return operation.performOperation();
         } finally {
-            mSynchronizer.syncEnd(account);
+            mSynchronizer.syncEnd(operation.getAccount());
         }
     }
 
@@ -389,23 +377,6 @@
         return false;
     }
 
-    /**
-     * Determine which content types are set to sync for an account.
-     * @param account The account whose sync settings we're looking for.
-     * @param authorities All possible authorities we could care about.
-     * @return The authorities for the content types we want to sync for account.
-     */
-    private static Set<String> getAuthoritiesToSync(final android.accounts.Account account,
-            final String[] authorities) {
-        final HashSet<String> authsToSync = new HashSet();
-        for (final String authority : authorities) {
-            if (ContentResolver.getSyncAutomatically(account, authority)) {
-                authsToSync.add(authority);
-            }
-        }
-        return authsToSync;
-    }
-
     static public GalResult searchGal(final Context context, final long accountId,
                                       final String filter, final int limit) {
         final EasSearchGal operation = new EasSearchGal(context, accountId, filter, limit);
@@ -422,4 +393,77 @@
         }
     }
 
+    /**
+     * Converts from an EasOperation status to a status code defined in EmailServiceStatus.
+     * This is used to communicate the status of a sync operation to the caller.
+     * @param easStatus result returned from an EasOperation
+     * @return EmailServiceStatus
+     */
+    private int convertToEmailServiceStatus(int easStatus) {
+        switch (easStatus) {
+            case EasOperation.RESULT_MIN_OK_RESULT:
+                return EmailServiceStatus.SUCCESS;
+
+            case EasOperation.RESULT_ABORT:
+            case EasOperation.RESULT_RESTART:
+                // This should only happen if a ping is interruped for some reason. We would not
+                // expect see that here, since this should only be called for a sync.
+                LogUtils.e(TAG, "unexpected easStatus %d", easStatus);
+                return EmailServiceStatus.SUCCESS;
+
+            case EasOperation.RESULT_TOO_MANY_REDIRECTS:
+                return EmailServiceStatus.INTERNAL_ERROR;
+
+            case EasOperation.RESULT_NETWORK_PROBLEM:
+                // This is due to an IO error, we need the caller to know about this so that it
+                // can let the syncManager know.
+                return EmailServiceStatus.IO_ERROR;
+
+            case EasOperation.RESULT_FORBIDDEN:
+            case EasOperation.RESULT_AUTHENTICATION_ERROR:
+                return EmailServiceStatus.LOGIN_FAILED;
+
+            case EasOperation.RESULT_PROVISIONING_ERROR:
+                return EmailServiceStatus.PROVISIONING_ERROR;
+
+            case EasOperation.RESULT_CLIENT_CERTIFICATE_REQUIRED:
+                return EmailServiceStatus.CLIENT_CERTIFICATE_ERROR;
+
+            case EasOperation.RESULT_PROTOCOL_VERSION_UNSUPPORTED:
+                return EmailServiceStatus.PROTOCOL_ERROR;
+
+            case EasOperation.RESULT_INITIALIZATION_FAILURE:
+            case EasOperation.RESULT_HARD_DATA_FAILURE:
+            case EasOperation.RESULT_OTHER_FAILURE:
+                return EmailServiceStatus.INTERNAL_ERROR;
+
+            case EasOperation.RESULT_NON_FATAL_ERROR:
+                // We do not expect to see this error here: This should be consumed in
+                // EasFullSyncOperation. The only case this occurs in is when we try to send
+                // a message in the outbox, and there's some problem with the message locally
+                // that prevents it from being sent. We return a
+                LogUtils.e(TAG, "unexpected easStatus %d", easStatus);
+                return EmailServiceStatus.SUCCESS;
+        }
+        LogUtils.e(TAG, "Unexpected easStatus %d");
+        return EmailServiceStatus.INTERNAL_ERROR;
+    }
+
+
+    /**
+     * Determine which content types are set to sync for an account.
+     * @param account The account whose sync settings we're looking for.
+     * @param authorities All possible authorities we could care about.
+     * @return The authorities for the content types we want to sync for account.
+     */
+    public static Set<String> getAuthoritiesToSync(final android.accounts.Account account,
+                                                    final String[] authorities) {
+        final HashSet<String> authsToSync = new HashSet();
+        for (final String authority : authorities) {
+            if (ContentResolver.getSyncAutomatically(account, authority)) {
+                authsToSync.add(authority);
+            }
+        }
+        return authsToSync;
+    }
 }
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 7fbaa66..d46fb8c 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -16,7 +16,6 @@
 
 package com.android.exchange.service;
 
-import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationManager;
@@ -25,7 +24,6 @@
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
@@ -36,9 +34,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -47,41 +42,22 @@
 import com.android.emailcommon.provider.Account;
 import com.android.emailcommon.provider.EmailContent;
 import com.android.emailcommon.provider.EmailContent.AccountColumns;
-import com.android.emailcommon.provider.EmailContent.Message;
 import com.android.emailcommon.provider.EmailContent.MessageColumns;
 import com.android.emailcommon.provider.EmailContent.SyncColumns;
-import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.provider.Mailbox;
-import com.android.emailcommon.service.EmailServiceProxy;
 import com.android.emailcommon.service.EmailServiceStatus;
 import com.android.emailcommon.service.IEmailService;
-import com.android.emailcommon.service.IEmailServiceCallback;
-import com.android.emailcommon.service.SearchParams;
 import com.android.emailcommon.service.ServiceProxy;
 import com.android.emailcommon.utility.IntentUtilities;
-import com.android.emailcommon.utility.Utility;
 import com.android.exchange.Eas;
 import com.android.exchange.R;
-import com.android.exchange.adapter.PingParser;
-import com.android.exchange.eas.EasAutoDiscover;
-import com.android.exchange.eas.EasSendMeetingResponse;
-import com.android.exchange.eas.EasSyncContacts;
-import com.android.exchange.eas.EasSyncCalendar;
 import com.android.exchange.eas.EasFolderSync;
-import com.android.exchange.eas.EasLoadAttachment;
 import com.android.exchange.eas.EasMoveItems;
 import com.android.exchange.eas.EasOperation;
-import com.android.exchange.eas.EasOutboxSync;
 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;
 
-import java.util.HashMap;
-import java.util.HashSet;
-
 /**
  * TODO: Most of this is deprecated.
  * This is needed to handle syncs, but pings and IEmailService functionality will be in EasService.
@@ -98,41 +74,27 @@
 
     private static final String TAG = Eas.LOG_TAG;
 
-    /**
-     * Temporary while converting to EasService. Do not check in set to true.
-     * When true, delegates various operations to {@link EasService}, for use while developing the
-     * new service.
-     * The two following fields are used to support what happens when this is true.
-     */
-    private static final boolean DELEGATE_TO_EAS_SERVICE = false;
     private IEmailService mEasService;
     private ServiceConnection mConnection;
 
     private static final String EXTRA_START_PING = "START_PING";
     private static final String EXTRA_PING_ACCOUNT = "PING_ACCOUNT";
+
+    // TODO: Do we need to use this?
     private static final long SYNC_ERROR_BACKOFF_MILLIS = 5 * DateUtils.MINUTE_IN_MILLIS;
 
     /**
+     * TODO: restore this functionality.
      * The amount of time between periodic syncs intended to ensure that push hasn't died.
      */
     private static final long KICK_SYNC_INTERVAL =
             DateUtils.HOUR_IN_MILLIS / DateUtils.SECOND_IN_MILLIS;
-
     /** Controls whether we do a periodic "kick" to restart the ping. */
     private static final boolean SCHEDULE_KICK = true;
 
-    /** Projection used for getting email address for an account. */
-    private static final String[] ACCOUNT_EMAIL_PROJECTION = { AccountColumns.EMAIL_ADDRESS };
-
     private static final Object sSyncAdapterLock = new Object();
     private static AbstractThreadedSyncAdapter sSyncAdapter = null;
 
-    // Value for a message's server id when sending fails.
-    public static final int SEND_FAILED = 1;
-    public static final String MAILBOX_KEY_AND_NOT_SEND_FAILED =
-            MessageColumns.MAILBOX_KEY + "=? and (" + SyncColumns.SERVER_ID + " is null or " +
-            SyncColumns.SERVER_ID + "!=" + SEND_FAILED + ')';
-
     public EmailSyncAdapterService() {
         super();
     }
@@ -148,31 +110,25 @@
         LogUtils.v(TAG, "onCreate()");
         super.onCreate();
         startService(new Intent(this, EmailSyncAdapterService.class));
-        if (DELEGATE_TO_EAS_SERVICE) {
-            // TODO: This block is temporary to support the transition to EasService.
-            mConnection = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name,  IBinder binder) {
-                    mEasService = IEmailService.Stub.asInterface(binder);
-                }
+        mConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name,  IBinder binder) {
+                mEasService = IEmailService.Stub.asInterface(binder);
+            }
 
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    mEasService = null;
-                }
-            };
-            bindService(new Intent(this, EasService.class), mConnection, Context.BIND_AUTO_CREATE);
-        }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mEasService = null;
+            }
+        };
+        bindService(new Intent(this, EasService.class), mConnection, Context.BIND_AUTO_CREATE);
     }
 
     @Override
     public void onDestroy() {
         LogUtils.v(TAG, "onDestroy()");
         super.onDestroy();
-        if (DELEGATE_TO_EAS_SERVICE) {
-            // TODO: This block is temporary to support the transition to EasService.
-            unbindService(mConnection);
-        }
+        unbindService(mConnection);
     }
 
     @Override
@@ -240,7 +196,7 @@
             final ContentResolver cr = context.getContentResolver();
 
             // Get the EmailContent Account
-            // XXX shouldn't this functionality live in Account, not here?
+            // TODO shouldn't this functionality live in Account, not here?
             final Account account;
             final Cursor accountCursor = cr.query(Account.CONTENT_URI, Account.CONTENT_PROJECTION,
                     AccountColumns.EMAIL_ADDRESS + "=?", new String[] {acct.name}, null);
@@ -259,27 +215,11 @@
                 }
             }
 
-            // Figure out what we want to sync, based on the extras and our account sync status.
-            final boolean isInitialSync = EmailContent.isInitialSyncKey(account.mSyncKey);
-            final long[] mailboxIds = Mailbox.getMailboxIdsFromBundle(extras);
-            final int mailboxType = extras.getInt(Mailbox.SYNC_EXTRA_MAILBOX_TYPE,
-                    Mailbox.TYPE_NONE);
-
             // Push only means this sync request should only refresh the ping (either because
             // settings changed, or we need to restart it for some reason).
             final boolean pushOnly = Mailbox.isPushOnlyExtras(extras);
-            // Account only means just do a FolderSync.
-            final boolean accountOnly = Mailbox.isAccountOnlyExtras(extras);
 
-            // A "full sync" means that we didn't request a more specific type of sync.
-            final boolean isFullSync = (!pushOnly && !accountOnly && mailboxIds == null &&
-                    mailboxType == Mailbox.TYPE_NONE);
-
-            // A FolderSync is necessary for full sync, initial sync, and account only sync.
-            final boolean isFolderSync = (isFullSync || isInitialSync || accountOnly);
-
-            // If we're just twiddling the push, we do the lightweight thing and bail early.
-            if (pushOnly && !isFolderSync) {
+            if (pushOnly) {
                 LogUtils.d(TAG, "onPerformSync: mailbox push only");
                 if (mEasService != null) {
                     try {
@@ -287,220 +227,25 @@
                         return;
                     } catch (final RemoteException re) {
                         LogUtils.e(TAG, re, "While trying to pushModify within onPerformSync");
+                        // TODO: how to handle this?
                     }
                 }
                 return;
-            }
-
-            int operationResult = 0;
-            try {
-                // Perform a FolderSync if necessary.
-                // TODO: We permit FolderSync even during security hold, because it's necessary to
-                // resolve some holds. Ideally we would only do it for the holds that require it.
-                if (isFolderSync) {
-                    final EasFolderSync folderSync = new EasFolderSync(context, account);
-                    operationResult = folderSync.doFolderSync();
-                    if (operationResult < 0) {
-                        return;
-                    }
-                }
-
-                // Do not permit further syncs if we're on security hold.
-                if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
-                    return;
-                }
-
-                // Perform email upsync for this account. Moves first, then state changes.
-                if (!isInitialSync) {
-                    EasMoveItems move = new EasMoveItems(context, account);
-                    operationResult = move.upsyncMovedMessages();
-                    if (operationResult < 0) {
-                        return;
-                    }
-
-                    // TODO: EasSync should eventually handle both up and down; for now, it's used
-                    // purely for upsync. This is not for sending, it's for upsyncing moves and
-                    // flag updates.
-                    EasSync upsync = new EasSync(context, account);
-                    operationResult = upsync.upsync();
-                    if (operationResult < 0) {
-                        return;
-                    }
-                }
-
-                if (mailboxIds != null) {
-                    final boolean hasCallbackMethod =
-                            extras.containsKey(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_METHOD);
-                    // Sync the mailbox that was explicitly requested.
-                    for (final long mailboxId : mailboxIds) {
-                        if (hasCallbackMethod) {
-                            EmailServiceStatus.syncMailboxStatus(cr, extras, mailboxId,
-                                    EmailServiceStatus.IN_PROGRESS, 0,
-                                    UIProvider.LastSyncResult.SUCCESS);
-                        }
-                        operationResult = syncMailbox(context, cr, acct, account, mailboxId,
-                                extras, syncResult, null, true);
-                        if (hasCallbackMethod) {
-                            EmailServiceStatus.syncMailboxStatus(cr, extras,
-                                    mailboxId,EmailServiceStatus.SUCCESS, 0,
-                                    EasOperation.translateSyncResultToUiResult(operationResult));
-                        }
-
-                        if (operationResult < 0) {
-                            break;
-                        }
-                    }
-                } else if (!accountOnly && !pushOnly) {
-                    // We have to sync multiple folders.
-                    final Cursor c;
-                    if (isFullSync) {
-                        // Full account sync includes all mailboxes that participate in system sync.
-                        c = Mailbox.getMailboxIdsForSync(cr, account.mId);
-                    } else {
-                        // Type-filtered sync should only get the mailboxes of a specific type.
-                        c = Mailbox.getMailboxIdsForSyncByType(cr, account.mId, mailboxType);
-                    }
-                    if (c != null) {
-                        try {
-                            final HashSet<String> authsToSync = getAuthsToSync(acct);
-                            while (c.moveToNext()) {
-                                operationResult = syncMailbox(context, cr, acct, account,
-                                        c.getLong(0), extras, syncResult, authsToSync, false);
-                                if (operationResult < 0) {
-                                    break;
-                                }
-                            }
-                        } finally {
-                            c.close();
-                        }
-                    }
-                }
-            } finally {
-                if (operationResult < 0) {
-                    EasFolderSync.writeResultToSyncResult(operationResult, syncResult);
-                    // If any operations had an auth error, notify the user.
-                    // Note that provisioning errors should have already triggered the policy
-                    // notification, so suppress those from showing the auth notification.
+            } else {
+                try {
+                    final int result = mEasService.sync(account.mId, extras);
+                    writeResultToSyncResult(result, syncResult);
                     if (syncResult.stats.numAuthExceptions > 0 &&
-                            operationResult != EasOperation.RESULT_PROVISIONING_ERROR) {
+                            result != EmailServiceStatus.PROVISIONING_ERROR) {
                         showAuthNotification(account.mId, account.mEmailAddress);
                     }
+                } catch (RemoteException e) {
+                     LogUtils.e(TAG, e, "While trying to pushModify within onPerformSync");
                 }
-
-                LogUtils.d(TAG, "onPerformSync: finished");
             }
+
+            LogUtils.d(TAG, "onPerformSync: finished");
         }
-
-        /**
-         * Update the mailbox's sync status with the provider and, if we're finished with the sync,
-         * write the last sync time as well.
-         * @param context Our {@link Context}.
-         * @param mailbox The mailbox whose sync status to update.
-         * @param cv A {@link ContentValues} object to use for updating the provider.
-         * @param syncStatus The status for the current sync.
-         */
-        private void updateMailbox(final Context context, final Mailbox mailbox,
-                final ContentValues cv, final int syncStatus) {
-            cv.put(Mailbox.UI_SYNC_STATUS, syncStatus);
-            if (syncStatus == EmailContent.SYNC_STATUS_NONE) {
-                cv.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
-            }
-            mailbox.update(context, cv);
-        }
-
-        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 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 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 EasSyncBase.RESULT_DONE;
-            }
-
-            if (mailbox.mType == Mailbox.TYPE_DRAFTS) {
-                // TODO: Because we don't have bidirectional sync working, trying to downsync
-                // the drafts folder is confusing. b/11158759
-                // For now, just disable all syncing of DRAFTS type folders.
-                // Automatic syncing should always be disabled, but we also stop it here to ensure
-                // 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 EasSyncBase.RESULT_DONE;
-            }
-
-            // 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.
-            if (mailbox.mType == Mailbox.TYPE_OUTBOX || mailbox.isSyncable()) {
-                final ContentValues cv = new ContentValues(2);
-                updateMailbox(context, mailbox, cv, isMailboxSync ?
-                        EmailContent.SYNC_STATUS_USER : EmailContent.SYNC_STATUS_BACKGROUND);
-                try {
-                    if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
-                        return syncOutbox(context, cr, account, mailbox);
-                    }
-                    final EasSyncBase operation = new EasSyncBase(context, account, mailbox);
-                    return operation.performOperation();
-                } finally {
-                    updateMailbox(context, mailbox, cv, EmailContent.SYNC_STATUS_NONE);
-                }
-            }
-
-            return EasSyncBase.RESULT_DONE;
-        }
-    }
-
-    private int syncOutbox(Context context, ContentResolver cr, Account account, Mailbox mailbox) {
-        // Get a cursor to Outbox messages
-        final Cursor c = cr.query(Message.CONTENT_URI,
-                Message.CONTENT_PROJECTION, MAILBOX_KEY_AND_NOT_SEND_FAILED,
-                new String[] {Long.toString(mailbox.mId)}, null);
-        try {
-            // Loop through the messages, sending each one
-            while (c.moveToNext()) {
-                final Message message = new Message();
-                message.restore(c);
-                if (Utility.hasUnloadedAttachments(context, message.mId)) {
-                    // We'll just have to wait on this...
-                    continue;
-                }
-
-                // TODO: Fix -- how do we want to signal to UI that we started syncing?
-                // Note the entire callback mechanism here needs improving.
-                //sendMessageStatus(message.mId, null, EmailServiceStatus.IN_PROGRESS, 0);
-
-                EasOperation op = new EasOutboxSync(context, account, message, true);
-                int result = op.performOperation();
-                if (result == EasOutboxSync.RESULT_ITEM_NOT_FOUND) {
-                    // This can happen if we are using smartReply, and the message we are referring
-                    // to has disappeared from the server. Try again with smartReply disabled.
-                    op = new EasOutboxSync(context, account, message, false);
-                    result = op.performOperation();
-                }
-                // If we got some connection error or other fatal error, terminate the sync.
-                if (result != EasOutboxSync.RESULT_OK &&
-                    result != EasOutboxSync.RESULT_NON_FATAL_ERROR &&
-                    result > EasOutboxSync.RESULT_OP_SPECIFIC_ERROR_RESULT) {
-                    LogUtils.w(TAG, "Aborting outbox sync for error %d", result);
-                    return result;
-                }
-            }
-        } finally {
-            // TODO: Some sort of sendMessageStatus() is needed here.
-            c.close();
-        }
-        return EasOutboxSync.RESULT_OK;
     }
 
     private void showAuthNotification(long accountId, String accountName) {
@@ -538,43 +283,50 @@
     }
 
     /**
-     * Determine which content types are set to sync for an account.
-     * @param account The account whose sync settings we're looking for.
-     * @return The authorities for the content types we want to sync for account.
+     * Interpret a result code from an {@link IEmailService.sync()} 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.
      */
-    private static HashSet<String> getAuthsToSync(final android.accounts.Account account) {
-        final HashSet<String> authsToSync = new HashSet();
-        if (ContentResolver.getSyncAutomatically(account, EmailContent.AUTHORITY)) {
-            authsToSync.add(EmailContent.AUTHORITY);
-        }
-        if (ContentResolver.getSyncAutomatically(account, CalendarContract.AUTHORITY)) {
-            authsToSync.add(CalendarContract.AUTHORITY);
-        }
-        if (ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY)) {
-            authsToSync.add(ContactsContract.AUTHORITY);
-        }
-        return authsToSync;
-    }
+    public static boolean writeResultToSyncResult(final int result, final SyncResult syncResult) {
+        switch (result) {
+            case EmailServiceStatus.SUCCESS:
+                return false;
 
-    /**
-     * Schedule to have a ping start some time in the future. This is used when we encounter an
-     * error, and properly should be a more full featured back-off, but for the short run, just
-     * waiting a few minutes at least avoids burning battery.
-     * @param amAccount The account that needs to be pinged.
-     * @param delay The time in milliseconds to wait before requesting the ping-only sync. Note that
-     *              it may take longer than this before the ping actually happens, since there's two
-     *              layers of waiting ({@link AlarmManager} can choose to wait longer, as can the
-     *              SyncManager).
-     */
-    private void scheduleDelayedPing(final android.accounts.Account amAccount, final long delay) {
-        final Intent intent = new Intent(this, EmailSyncAdapterService.class);
-        intent.setAction(Eas.EXCHANGE_SERVICE_INTENT_ACTION);
-        intent.putExtra(EXTRA_START_PING, true);
-        intent.putExtra(EXTRA_PING_ACCOUNT, amAccount);
-        final PendingIntent pi = PendingIntent.getService(this, 0, intent,
-                PendingIntent.FLAG_ONE_SHOT);
-        final AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
-        final long atTime = SystemClock.elapsedRealtime() + delay;
-        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, atTime, pi);
+            case EmailServiceStatus.REMOTE_EXCEPTION:
+            case EmailServiceStatus.LOGIN_FAILED:
+            case EmailServiceStatus.SECURITY_FAILURE:
+            case EmailServiceStatus.CLIENT_CERTIFICATE_ERROR:
+            case EmailServiceStatus.ACCESS_DENIED:
+                    syncResult.stats.numAuthExceptions = 1;
+                return true;
+
+            case EmailServiceStatus.HARD_DATA_ERROR:
+            case EmailServiceStatus.INTERNAL_ERROR:
+                syncResult.databaseError = true;
+                return true;
+
+            case EmailServiceStatus.CONNECTION_ERROR:
+            case EmailServiceStatus.IO_ERROR:
+                syncResult.stats.numIoExceptions = 1;
+                return true;
+
+            case EmailServiceStatus.TOO_MANY_REDIRECTS:
+                syncResult.tooManyRetries = true;
+                return true;
+
+            case EmailServiceStatus.IN_PROGRESS:
+            case EmailServiceStatus.MESSAGE_NOT_FOUND:
+            case EmailServiceStatus.ATTACHMENT_NOT_FOUND:
+            case EmailServiceStatus.FOLDER_NOT_DELETED:
+            case EmailServiceStatus.FOLDER_NOT_RENAMED:
+            case EmailServiceStatus.FOLDER_NOT_CREATED:
+            case EmailServiceStatus.ACCOUNT_UNINITIALIZED:
+            case EmailServiceStatus.PROTOCOL_ERROR:
+                LogUtils.e(TAG, "Unexpected sync result %d", result);
+                return false;
+        }
+        return false;
     }
 }
diff --git a/src/com/android/exchange/service/PingTask.java b/src/com/android/exchange/service/PingTask.java
index c68ab8f..5852fe6 100644
--- a/src/com/android/exchange/service/PingTask.java
+++ b/src/com/android/exchange/service/PingTask.java
@@ -72,7 +72,7 @@
             // If we get any sort of exception here, treat it like the ping returned a connection
             // failure.
             LogUtils.e(TAG, e, "Ping exception for account %d", mOperation.getAccountId());
-            pingStatus = EasOperation.RESULT_REQUEST_FAILURE;
+            pingStatus = EasOperation.RESULT_NETWORK_PROBLEM;
         }
         LogUtils.i(TAG, "Ping task ending with status: %d", pingStatus);