Merge "Code refactoring, prep for contacts/calendar." into jb-ub-mail-ur10
diff --git a/src/com/android/exchange/service/EasAccountValidator.java b/src/com/android/exchange/service/EasAccountValidator.java
index e867ded..63ce9b9 100644
--- a/src/com/android/exchange/service/EasAccountValidator.java
+++ b/src/com/android/exchange/service/EasAccountValidator.java
@@ -61,8 +61,6 @@
             Eas.SUPPORTED_PROTOCOL_EX2007, Eas.SUPPORTED_PROTOCOL_EX2007_SP1,
             Eas.SUPPORTED_PROTOCOL_EX2010, Eas.SUPPORTED_PROTOCOL_EX2010_SP1);
 
-    /** mAccount.mProtocolVersion, as a double. If the version is unknown, this will be 0.0. */
-    private double mProtocolVersionDouble;
     /** The number of times we've been redirected so far. */
     private int mRedirectCount;
 
@@ -81,11 +79,6 @@
     public EasAccountValidator(final Context context, final Account account,
             final HostAuth hostAuth) {
         super(context, account, hostAuth);
-        if (account.mProtocolVersion != null) {
-            mProtocolVersionDouble = Eas.getProtocolVersionDouble(account.mProtocolVersion);
-        } else {
-            mProtocolVersionDouble = 0.0d;
-        }
         mRedirectCount = 0;
     }
 
@@ -115,19 +108,19 @@
         if (newProtocolVersion == null) {
             LogUtils.w(TAG, "No supported EAS versions: %s", supportedVersions);
             // TODO: if mAccount.isSaved(), we should delete the account.
-            mProtocolVersionDouble = 0.0d;
             return false;
-        } else {
-            mProtocolVersionDouble = Eas.getProtocolVersionDouble(newProtocolVersion);
         }
 
         // Update our account with the new protocol version.
         final boolean protocolChanged = !newProtocolVersion.equals(mAccount.mProtocolVersion);
-        mAccount.mProtocolVersion = newProtocolVersion;
+        if (protocolChanged) {
+            mAccount.mProtocolVersion = newProtocolVersion;
+            uncacheProtocolVersion();
+        }
 
         // Fixup search flags, if they're not set.
         final boolean flagsChanged;
-        if (mProtocolVersionDouble >= 12.0) {
+        if (getProtocolVersion() >= 12.0) {
             int oldFlags = mAccount.mFlags;
             mAccount.mFlags |= Account.FLAGS_SUPPORTS_GLOBAL_SEARCH + Account.FLAGS_SUPPORTS_SEARCH;
             flagsChanged = (oldFlags != mAccount.mFlags);
@@ -279,7 +272,7 @@
     }
 
     private String getPolicyType() {
-        return (mProtocolVersionDouble >=
+        return (getProtocolVersion() >=
             Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
     }
 
@@ -364,7 +357,7 @@
     private ProvisionParser canProvision() throws IOException {
         final Serializer s = new Serializer();
         s.start(Tags.PROVISION_PROVISION);
-        if (mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) {
+        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) {
             // Send settings information in 14.1 and greater
             s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET);
             s.data(Tags.SETTINGS_MODEL, Build.MODEL);
@@ -390,7 +383,7 @@
                     // The PolicySet in the ProvisionParser will have the requirements for all KNOWN
                     // policies.  If others are required, hasSupportablePolicySet will be false
                     if (pp.hasSupportablePolicySet() &&
-                            mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+                            getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
                         // In EAS 14.0, we need the final security key in order to use the settings
                         // command
                         final String policyKey = acknowledgeProvision(pp.getSecuritySyncKey(),
@@ -468,7 +461,7 @@
             // to the server and get the final policy key
             // NOTE: For EAS 14.0, we already have the acknowledgment in the ProvisionParser
             String securitySyncKey;
-            if (mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+            if (getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
                 securitySyncKey = pp.getSecuritySyncKey();
             } else {
                 securitySyncKey = acknowledgeProvision(pp.getSecuritySyncKey(),
@@ -560,7 +553,7 @@
                             resultCode = MessagingException.SECURITY_POLICIES_REQUIRED;
                             bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET,
                                     pp.getPolicy());
-                            if (mProtocolVersionDouble == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+                            if (getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
                                 mAccount.mSecuritySyncKey = pp.getSecuritySyncKey();
                                 if (!sendSettings()) {
                                     LogUtils.i(TAG, "Denied access: %s",
diff --git a/src/com/android/exchange/service/EasMailboxSyncHandler.java b/src/com/android/exchange/service/EasMailboxSyncHandler.java
index f3eab9e..aaf3454 100644
--- a/src/com/android/exchange/service/EasMailboxSyncHandler.java
+++ b/src/com/android/exchange/service/EasMailboxSyncHandler.java
@@ -71,10 +71,8 @@
                 s.start(Tags.SYNC_COLLECTIONS);
                 s.start(Tags.SYNC_COLLECTION);
 
-                final Double protocolVersionDouble =
-                        Eas.getProtocolVersionDouble(getProtocolVersion());
                 // The "Class" element is removed in EAS 12.1 and later versions
-                if (protocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
+                if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
                     s.data(Tags.SYNC_CLASS, "Email");
                 }
                 s.data(Tags.SYNC_SYNC_KEY, mMailbox.mSyncKey);
@@ -93,7 +91,7 @@
                 // Therefore, we don't send any options with the initial sync.
                 // Set the truncation amount, body preference, lookback, etc.
                 if (!initialSync) {
-                    sendSyncOptions(protocolVersionDouble, s, !fetchRequestList.isEmpty());
+                    sendSyncOptions(getProtocolVersion(), s, !fetchRequestList.isEmpty());
                     // TODO: Fix.
                     //EmailSyncAdapter.upsync(resolver, mailbox, s);
                 }
diff --git a/src/com/android/exchange/service/EasOutboxSyncHandler.java b/src/com/android/exchange/service/EasOutboxSyncHandler.java
index 5595231..b2f8cff 100644
--- a/src/com/android/exchange/service/EasOutboxSyncHandler.java
+++ b/src/com/android/exchange/service/EasOutboxSyncHandler.java
@@ -1,13 +1,10 @@
 package com.android.exchange.service;
 
-import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
-import android.content.SyncResult;
 import android.database.Cursor;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.os.Bundle;
 import android.text.format.DateUtils;
 
 import com.android.emailcommon.TrafficFlags;
@@ -20,8 +17,8 @@
 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.EmailServiceStatus;
 import com.android.emailcommon.utility.Utility;
 import com.android.exchange.CommandStatusException.CommandStatus;
 import com.android.exchange.Eas;
@@ -48,7 +45,7 @@
 /**
  * Performs an Exchange Outbox sync, i.e. sends all mail from the Outbox.
  */
-public class EasOutboxSyncHandler extends EasSyncHandler {
+public class EasOutboxSyncHandler extends EasServerConnection {
     // Value for a message's server id when sending fails.
     public static final int SEND_FAILED = 1;
 
@@ -64,24 +61,23 @@
     // failure would probably generate an Exception before timing out anyway
     public static final long SEND_MAIL_TIMEOUT = 15 * DateUtils.MINUTE_IN_MILLIS;
 
+    private final Mailbox mMailbox;
     private final File mCacheDir;
 
-    public EasOutboxSyncHandler(final Context context, final ContentResolver contentResolver,
-                final Account account, final Mailbox mailbox, final Bundle syncExtras,
-                final SyncResult syncResult) {
-        super(context, contentResolver, account, mailbox, syncExtras, syncResult);
+    public EasOutboxSyncHandler(final Context context, final Account account,
+            final Mailbox mailbox) {
+        super(context, account, HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv));
+        mMailbox = mailbox;
         mCacheDir = context.getCacheDir();
     }
 
-    @Override
-    public SyncStatus performSync() {
+    public void performSync() {
         // Use SMTP flags for sending mail
         TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(mContext, mAccount));
         // Get a cursor to Outbox messages
         final Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
                 Message.CONTENT_PROJECTION, MAILBOX_KEY_AND_NOT_SEND_FAILED,
                 new String[] {Long.toString(mMailbox.mId)}, null);
-        SyncStatus status = SyncStatus.SUCCESS;
         try {
             // Loop through the messages, sending each one
             while (c.moveToNext()) {
@@ -92,32 +88,19 @@
                     continue;
                 }
 
-                sendMessageStatus(message.mId, null, EmailServiceStatus.IN_PROGRESS, 0);
+                // 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);
 
-                status = sendOneMessage(message,
-                        SmartSendInfo.getSmartSendInfo(mContext, mAccount, message));
-                // TODO: Improve handling of individual message send responses.
-                switch (status) {
-                    case SUCCESS:
-                        break;
-                    case FAILURE_IO:
-                        break;
-                    case FAILURE_LOGIN:
-                        break;
-                    case FAILURE_SECURITY:
-                        break;
-                    case FAILURE_MESSAGE:
-                        break;
-                    case FAILURE_OTHER:
-                        break;
+                if (!sendOneMessage(message,
+                        SmartSendInfo.getSmartSendInfo(mContext, mAccount, message))) {
+                    break;
                 }
             }
         } finally {
             // TODO: Some sort of sendMessageStatus() is needed here.
             c.close();
         }
-
-        return status;
     }
 
     /**
@@ -406,15 +389,16 @@
      * @param message The message to send.
      * @param smartSendInfo The SmartSendInfo for this message, or null if we don't have or don't
      *      want to use smart send.
-     * @return A status value to indicate success or manner of failure for this operation.
+     * @return Whether or not sending this message succeeded.
+     * TODO: Improve how we handle the types of failures. I've left the old error codes in as TODOs
+     * for future reference.
      */
-    private SyncStatus sendOneMessage(final Message message,
-            final SmartSendInfo smartSendInfo) {
+    private boolean sendOneMessage(final Message message, final SmartSendInfo smartSendInfo) {
         final File tmpFile;
         try {
             tmpFile = File.createTempFile("eas_", "tmp", mCacheDir);
         } catch (final IOException e) {
-            return SyncStatus.FAILURE_IO;
+            return false; // TODO: Handle SyncStatus.FAILURE_IO;
         }
 
         final EasResponse resp;
@@ -424,14 +408,14 @@
         final int modeTag = getModeTag(isEas14, smartSendInfo);
         try {
             if (!writeMessageToTempFile(tmpFile, message, smartSendInfo)) {
-                return SyncStatus.FAILURE_IO;
+                return false; // TODO: Handle SyncStatus.FAILURE_IO;
             }
 
             final FileInputStream fileStream;
             try {
                 fileStream = new FileInputStream(tmpFile);
             } catch (final FileNotFoundException e) {
-                return SyncStatus.FAILURE_IO;
+                return false; // TODO: Handle SyncStatus.FAILURE_IO;
             }
             try {
 
@@ -462,7 +446,7 @@
                 try {
                     resp = sendHttpClientPost(cmd, entity, SEND_MAIL_TIMEOUT);
                 } catch (final IOException e) {
-                    return SyncStatus.FAILURE_IO;
+                    return false; // TODO: Handle SyncStatus.FAILURE_IO;
                 }
 
             } finally {
@@ -492,19 +476,19 @@
                         // The parser holds the status
                         final int status = p.getStatus();
                         if (CommandStatus.isNeedsProvisioning(status)) {
-                            return SyncStatus.FAILURE_SECURITY;
+                            return false; // TODO: Handle SyncStatus.FAILURE_SECURITY;
                         } else if (status == CommandStatus.ITEM_NOT_FOUND &&
                                 smartSendInfo != null) {
                             // Let's retry without "smart" commands.
                             return sendOneMessage(message, null);
                         }
                         // TODO: Set syncServerId = SEND_FAILED in DB?
-                        return SyncStatus.FAILURE_MESSAGE;
+                        return false; // TODO: Handle SyncStatus.FAILURE_MESSAGE;
                     } catch (final EmptyStreamException e) {
                         // This is actually fine; an empty stream means SendMail succeeded
                     } catch (final IOException e) {
                         // Parsing failed in some other way.
-                        return SyncStatus.FAILURE_IO;
+                        return false; // TODO: Handle SyncStatus.FAILURE_IO;
                     }
                 }
             } else if (code == HttpStatus.SC_INTERNAL_SERVER_ERROR && smartSendInfo != null) {
@@ -512,9 +496,9 @@
                 return sendOneMessage(message, null);
             } else {
                 if (EasResponse.isAuthError(code)) {
-                    return SyncStatus.FAILURE_LOGIN;
+                    return false; // TODO: Handle SyncStatus.FAILURE_LOGIN;
                 } else if (EasResponse.isProvisionError(code)) {
-                    return SyncStatus.FAILURE_SECURITY;
+                    return false; // TODO: Handle SyncStatus.FAILURE_SECURITY;
                 }
             }
         } finally {
@@ -525,7 +509,7 @@
         // Delete the sent message.
         mContext.getContentResolver().delete(
                 ContentUris.withAppendedId(Message.CONTENT_URI, message.mId), null, null);
-        return SyncStatus.SUCCESS;
+        return true;
     }
 
     /**
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index 3b0ffa5..b87e128 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -78,6 +78,14 @@
     private boolean mStopped = false;
 
     /**
+     * The protocol version to use, as a double. This is a cached value based on the protocol
+     * version in {@link #mAccount}, so whenever that value is changed,
+     * {@link #uncacheProtocolVersion()} must be called.
+     */
+    private double mProtocolVersionDouble = 0.0d;
+
+
+    /**
      * We want to reuse {@link EmailClientConnectionManager} across different requests to the same
      * {@link HostAuth}. Since HostAuths have unique ids, we can use that as the cache key.
      * All access to the cache must be synchronized in theory, although in practice since we don't
@@ -192,10 +200,10 @@
     }
 
     /**
-     * Get the protocol version for an account, or a default if we can't determine it.
-     * @return The protocol version for account, as a String.
+     * Get the protocol version for {@link #mAccount}, or a default if we can't determine it.
+     * @return The protocol version for {@link #mAccount}, as a String.
      */
-    protected String getProtocolVersion() {
+    private String getProtocolVersionString() {
         if (mAccount.mProtocolVersion != null) {
             return mAccount.mProtocolVersion;
         }
@@ -203,13 +211,33 @@
     }
 
     /**
+     * If a sync causes us to update our protocol version, this function must be called so that
+     * subsequent calls to {@link #getProtocolVersion()} will do the right thing.
+     */
+    protected void uncacheProtocolVersion() {
+        mProtocolVersionDouble = 0.0d;
+    }
+
+    /**
+     * Get the protocol version for {@link #mAccount}, as a double. This function caches the result
+     * of looking up the value so that subsequent calls do not have to repeat that.
+     * @return The protocol version for {@link #mAccount}, as a double.
+     */
+    protected double getProtocolVersion() {
+        if (mProtocolVersionDouble == 0.0d) {
+            mProtocolVersionDouble = Eas.getProtocolVersionDouble(getProtocolVersionString());
+        }
+        return mProtocolVersionDouble;
+    }
+
+    /**
      * Set standard HTTP headers, using a policy key if required
      * @param method the method we are going to send
      * @param usePolicyKey whether or not a policy key should be sent in the headers
      */
     private void setHeaders(final HttpRequestBase method, final boolean usePolicyKey) {
         method.setHeader("Authorization", makeAuthString());
-        method.setHeader("MS-ASProtocolVersion", getProtocolVersion());
+        method.setHeader("MS-ASProtocolVersion", getProtocolVersionString());
         method.setHeader("User-Agent", USER_AGENT);
         method.setHeader("Accept-Encoding", "gzip");
         if (usePolicyKey) {
@@ -273,9 +301,7 @@
         // Send the proper Content-Type header; it's always wbxml except for messages when
         // the EAS protocol version is < 14.0
         // If entity is null (e.g. for attachments), don't set this header
-        final String protocolVersion = getProtocolVersion();
-        final Double protocolVersionDouble = Eas.getProtocolVersionDouble(protocolVersion);
-        if (msg && (protocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE)) {
+        if (msg && (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE)) {
             method.setHeader("Content-Type", "message/rfc822");
         } else if (entity != null) {
             method.setHeader("Content-Type", "application/vnd.ms-sync.wbxml");
diff --git a/src/com/android/exchange/service/EasSyncHandler.java b/src/com/android/exchange/service/EasSyncHandler.java
index b2af7a2..e65b0eb 100644
--- a/src/com/android/exchange/service/EasSyncHandler.java
+++ b/src/com/android/exchange/service/EasSyncHandler.java
@@ -14,7 +14,7 @@
  * Base class for performing a single sync action. It holds the state needed for all sync actions
  * (e.g. account and auth info, sync extras and results) and functions to communicate to with the
  * app UI.
- * Sublclasses must implement {@link #performSync}, but otherwise have no other requirements.
+ * Subclasses must implement {@link #performSync}, but otherwise have no other requirements.
  */
 public abstract class EasSyncHandler extends EasServerConnection {
     protected final ContentResolver mContentResolver;
@@ -71,9 +71,6 @@
                 case Mailbox.TYPE_MAIL:
                     return new EasMailboxSyncHandler(context, contentResolver, account, mailbox,
                             syncExtras, syncResult);
-                case Mailbox.TYPE_OUTBOX:
-                    return new EasOutboxSyncHandler(context, contentResolver, account, mailbox,
-                            syncExtras, syncResult);
             }
         }
         // Could not handle this sync.
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 4ed1c69..adb7572 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -498,20 +498,28 @@
                 } else {
                     inboxSyncHandler.performSync();
                 }
+
+                // TODO: Do an outbox sync as well?
             } else {
                 // Sync the mailbox that was explicitly requested.
                 final Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
-                final EasSyncHandler syncHandler = EasSyncHandler.getEasSyncHandler(context, cr,
-                        account, mailbox, extras, syncResult);
-
-                if (syncHandler != null) {
-                    syncHandler.performSync();
+                if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
+                    final EasOutboxSyncHandler outboxSyncHandler =
+                            new EasOutboxSyncHandler(context, account, mailbox);
+                    outboxSyncHandler.performSync();
                 } else {
-                    // We can't sync this mailbox, so just send the expected UI callbacks.
-                    EmailServiceStatus.syncMailboxStatus(cr, extras, mailboxId,
-                            EmailServiceStatus.IN_PROGRESS, 0);
-                    EmailServiceStatus.syncMailboxStatus(cr, extras, mailboxId,
-                            EmailServiceStatus.SUCCESS, 0);
+                    final EasSyncHandler syncHandler = EasSyncHandler.getEasSyncHandler(context, cr,
+                            account, mailbox, extras, syncResult);
+
+                    if (syncHandler != null) {
+                        syncHandler.performSync();
+                    } else {
+                        // We can't sync this mailbox, so just send the expected UI callbacks.
+                        EmailServiceStatus.syncMailboxStatus(cr, extras, mailboxId,
+                                EmailServiceStatus.IN_PROGRESS, 0);
+                        EmailServiceStatus.syncMailboxStatus(cr, extras, mailboxId,
+                                EmailServiceStatus.SUCCESS, 0);
+                    }
                 }
             }