Created an EasOperation for attachment loading.
This class has been enabled within EmailSyncAdapterService
although it was written as part of the move to EasService.

Change-Id: Ica21e06013925b68faef32624c164f26b94fa6de
(cherry picked from commit b31a8ded3eddd16ce7083ccf6c3d4774bc81b6ca)
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java
index 85ea33f..eef2ce6 100644
--- a/src/com/android/exchange/ExchangeService.java
+++ b/src/com/android/exchange/ExchangeService.java
@@ -132,8 +132,8 @@
         }
 
         @Override
-        public void loadAttachment(final IEmailServiceCallback callback, final long attachmentId,
-                final boolean background) throws RemoteException {
+        public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
+                final long attachmentId, final boolean background) throws RemoteException {
             Attachment att = Attachment.restoreAttachmentWithId(ExchangeService.this, attachmentId);
             log("loadAttachment " + attachmentId + ": " + att.mFileName);
             sendMessageRequest(new PartRequest(att, null, null));
diff --git a/src/com/android/exchange/adapter/ItemOperationsParser.java b/src/com/android/exchange/adapter/ItemOperationsParser.java
index b383993..deace34 100644
--- a/src/com/android/exchange/adapter/ItemOperationsParser.java
+++ b/src/com/android/exchange/adapter/ItemOperationsParser.java
@@ -15,7 +15,7 @@
 
 package com.android.exchange.adapter;
 
-import com.android.exchange.service.EasAttachmentLoader.ProgressCallback;
+import com.android.exchange.eas.EasLoadAttachment.ProgressCallback;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/com/android/exchange/eas/EasLoadAttachment.java b/src/com/android/exchange/eas/EasLoadAttachment.java
new file mode 100644
index 0000000..fe51a96
--- /dev/null
+++ b/src/com/android/exchange/eas/EasLoadAttachment.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.exchange.eas;
+
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.RemoteException;
+
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Attachment;
+import com.android.emailcommon.service.EmailServiceStatus;
+import com.android.emailcommon.service.IEmailServiceCallback;
+import com.android.emailcommon.utility.AttachmentUtilities;
+import com.android.exchange.CommandStatusException;
+import com.android.exchange.Eas;
+import com.android.exchange.EasResponse;
+import com.android.exchange.adapter.ItemOperationsParser;
+import com.android.exchange.adapter.Serializer;
+import com.android.exchange.adapter.Tags;
+import com.android.exchange.service.EasService;
+import com.android.exchange.utility.UriCodec;
+import com.android.mail.utils.LogUtils;
+
+import org.apache.http.HttpEntity;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class performs the heavy lifting of loading attachments from the Exchange server to the
+ * device in a local file.
+ * TODO: Add ability to call back to UI when this failed, and generally better handle error cases.
+ */
+public final class EasLoadAttachment extends EasOperation {
+
+    /** Result code indicating the sync completed correctly. */
+    public static final int RESULT_OK = 1;
+
+    /** Attachment Loading Errors **/
+    public static final int RESULT_LOAD_ATTACHMENT_INFO_ERROR = -100;
+    public static final int RESULT_ATTACHMENT_NO_LOCATION_ERROR = -101;
+    public static final int RESULT_ATTACHMENT_LOAD_MESSAGE_ERROR = -102;
+    public static final int RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR = -103;
+    public static final int RESULT_ATTACHMENT_RESPONSE_PARSING_ERROR = -104;
+
+    private final IEmailServiceCallback mCallback;
+    private final long mAttachmentId;
+
+    // These members are set in a future point in time outside of the constructor.
+    private int mFinalStatus = RESULT_OK;
+    private Attachment mAttachment;
+
+    /**
+     * Constructor for use with {@link EasService} when performing an actual sync.
+     * @param context Our {@link Context}.
+     * @param accountId The id of the account in question (i.e. its id in the database).
+     * @param attachmentId The local id of the attachment (i.e. its id in the database).
+     * @param callback The callback for any status updates.
+     */
+    public EasLoadAttachment(final Context context, final long accountId, final long attachmentId,
+            final IEmailServiceCallback callback) {
+        // The account is loaded before performOperation but it is not guaranteed to be available
+        // before then.
+        super(context, accountId);
+        mCallback = callback;
+        mAttachmentId = attachmentId;
+    }
+
+    /**
+     * Helper function that makes a callback for us within our implementation.
+     */
+    private static void doStatusCallback(final IEmailServiceCallback callback,
+            final long messageKey, final long attachmentId, final int status, final int progress) {
+        if (callback != null) {
+            try {
+                // loadAttachmentStatus is mart of IEmailService interface.
+                callback.loadAttachmentStatus(messageKey, attachmentId, status, progress);
+            } catch (final RemoteException e) {
+                LogUtils.e(LOG_TAG, "RemoteException in loadAttachment: %s", e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Helper class that is passed to other objects to perform callbacks for us.
+     */
+    public static class ProgressCallback {
+        private final IEmailServiceCallback mCallback;
+        private final EmailContent.Attachment mAttachment;
+
+        public ProgressCallback(final IEmailServiceCallback callback,
+                final EmailContent.Attachment attachment) {
+            mCallback = callback;
+            mAttachment = attachment;
+        }
+
+        public void doCallback(final int progress) {
+            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachment.mId,
+                    EmailServiceStatus.IN_PROGRESS, progress);
+        }
+    }
+
+    /**
+     * Encoder for Exchange 2003 attachment names.  They come from the server partially encoded,
+     * but there are still possible characters that need to be encoded (Why, MSFT, why?)
+     */
+    private static class AttachmentNameEncoder extends UriCodec {
+        @Override
+        protected boolean isRetained(final char c) {
+            // These four characters are commonly received in EAS 2.5 attachment names and are
+            // valid (verified by testing); we won't encode them
+            return c == '_' || c == ':' || c == '/' || c == '.';
+        }
+    }
+
+    /**
+     * Finish encoding attachment names for Exchange 2003.
+     * @param str A partially encoded string.
+     * @return The fully encoded version of str.
+     */
+    private static String encodeForExchange2003(final String str) {
+        final AttachmentNameEncoder enc = new AttachmentNameEncoder();
+        final StringBuilder sb = new StringBuilder(str.length() + 16);
+        enc.appendPartiallyEncoded(sb, str);
+        return sb.toString();
+    }
+
+    /**
+     * Finish encoding attachment names for Exchange 2003.
+     * @param syncResult The {@link SyncResult} that stores the result of the operation.
+     * @return A {@link EmailServiceStatus} code that indicates the result of the operation.
+     */
+    @Override
+    public int performOperation(final SyncResult syncResult) {
+        mAttachment = EmailContent.Attachment.restoreAttachmentWithId(mContext, mAttachmentId);
+        if (mAttachment == null) {
+            LogUtils.e(LOG_TAG, "Could not load attachment %d", mAttachmentId);
+            doStatusCallback(mCallback, -1, mAttachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
+                    0);
+            return RESULT_LOAD_ATTACHMENT_INFO_ERROR;
+        }
+        if (mAttachment.mLocation == null) {
+            LogUtils.e(LOG_TAG, "Attachment %d lacks a location", mAttachmentId);
+            doStatusCallback(mCallback, -1, mAttachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
+                    0);
+            return RESULT_ATTACHMENT_NO_LOCATION_ERROR;
+        }
+        final EmailContent.Message message = EmailContent.Message
+                .restoreMessageWithId(mContext, mAttachment.mMessageKey);
+        if (message == null) {
+            LogUtils.e(LOG_TAG, "Could not load message %d", mAttachment.mMessageKey);
+            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId,
+                    EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
+            return RESULT_ATTACHMENT_LOAD_MESSAGE_ERROR;
+        }
+
+        // First callback to let the client know that we have started the attachment load.
+        doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachment.mId,
+                EmailServiceStatus.IN_PROGRESS, 0);
+
+        final int return_value = super.performOperation(syncResult);
+
+        // Last callback to report results.  Note that we are using the status member variable
+        // to keep track of the status to be returned as super.performOperation() is not designed
+        // to return the most contextually relevant code.
+        if (mFinalStatus == RESULT_OK) {
+            // We'll just use the one that was returned to us since we didn't track anything
+            // interesting ourselves.
+            mFinalStatus = return_value;
+        }
+        doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachmentId, mFinalStatus, 0);
+
+        return return_value;
+    }
+
+    @Override
+    protected String getCommand() {
+        if (mAttachment == null) {
+            LogUtils.wtf(LOG_TAG, "Error, mAttachment is null");
+        }
+
+        final String cmd;
+        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+            // The operation is different in EAS 14.0 than in earlier versions
+            cmd = "ItemOperations";
+        } else {
+            final String location;
+            // For Exchange 2003 (EAS 2.5), we have to look for illegal chars in the file name
+            // that EAS sent to us!
+            if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
+                location = encodeForExchange2003(mAttachment.mLocation);
+            } else {
+                location = mAttachment.mLocation;
+            }
+            cmd = "GetAttachment&AttachmentName=" + location;
+        }
+        return cmd;
+    }
+
+    @Override
+    protected HttpEntity getRequestEntity() throws IOException {
+        if (mAttachment == null) {
+            LogUtils.wtf(LOG_TAG, "Error, mAttachment is null");
+        }
+
+        final HttpEntity entity;
+        final Serializer s = new Serializer();
+        if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+            s.start(Tags.ITEMS_ITEMS).start(Tags.ITEMS_FETCH);
+            s.data(Tags.ITEMS_STORE, "Mailbox");
+            s.data(Tags.BASE_FILE_REFERENCE, mAttachment.mLocation);
+            s.end().end().done(); // ITEMS_FETCH, ITEMS_ITEMS
+            entity = makeEntity(s);
+        } else {
+            // Older versions of the protocol have the attachment location in the command.
+            entity = null;
+        }
+        return entity;
+    }
+
+    /**
+     * Close, ignoring errors (as during cleanup)
+     * @param c a Closeable
+     */
+    private static void close(final Closeable c) {
+        try {
+            c.close();
+        } catch (IOException e) {
+            LogUtils.e(LOG_TAG, "IOException while cleaning up attachment: %s", e.getMessage());
+        }
+    }
+
+    /**
+     * Save away the contentUri for this Attachment and notify listeners
+     */
+    private boolean finishLoadAttachment(final EmailContent.Attachment attachment, final File file) {
+        final InputStream in;
+        try {
+            in = new FileInputStream(file);
+        } catch (final FileNotFoundException e) {
+            // Unlikely, as we just created it successfully, but log it.
+            LogUtils.e(LOG_TAG, "Could not open attachment file: %s", e.getMessage());
+            return false;
+        }
+        AttachmentUtilities.saveAttachment(mContext, in, attachment);
+        close(in);
+        return true;
+    }
+
+    /**
+     * Read the {@link EasResponse} and extract the attachment data, saving it to the provider.
+     * @param response The (successful) {@link EasResponse} containing the attachment data.
+     * @param syncResult The {@link SyncResult} that stores the result of the operation.
+     * @return A status code, from {@link EmailServiceStatus}, for this load.
+     */
+    @Override
+    protected int handleResponse(final EasResponse response, final SyncResult syncResult)
+            throws IOException, CommandStatusException {
+        // Some very basic error checking on the response object first.
+        // Our base class should be responsible for checking these errors but if the error
+        // checking is done in the override functions, we can be more specific about
+        // the errors that are being returned to the caller of performOperation().
+        if (response.isEmpty()) {
+            LogUtils.e(LOG_TAG, "Error, empty response.");
+            mFinalStatus = RESULT_REQUEST_FAILURE;
+            return mFinalStatus;
+        }
+
+        // This is a 2 step process.
+        // 1. Grab what came over the wire and write it to a temp file on disk.
+        // 2. Move the attachment to its final location.
+        final File tmpFile;
+        try {
+            tmpFile = File.createTempFile("eas_", "tmp", mContext.getCacheDir());
+        } catch (final IOException e) {
+            LogUtils.e(LOG_TAG, "Could not open temp file: %s", e.getMessage());
+            mFinalStatus = RESULT_REQUEST_FAILURE;
+            return mFinalStatus;
+        }
+
+        try {
+            final OutputStream os;
+            try {
+                os = new FileOutputStream(tmpFile);
+            } catch (final FileNotFoundException e) {
+                LogUtils.e(LOG_TAG, "Temp file not found: %s", e.getMessage());
+                mFinalStatus = RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
+                return mFinalStatus;
+            }
+            try {
+                final InputStream is = response.getInputStream();
+                try {
+                    // TODO: Right now we are explictly loading this from a class
+                    // that will be deprecated when we move over to EasService. When we start using
+                    // our internal class instead, there will be rippling side effect changes that
+                    // need to be made when this time comes.
+                    final ProgressCallback callback = new ProgressCallback(mCallback, mAttachment);
+                    final boolean success;
+                    if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
+                        final ItemOperationsParser parser = new ItemOperationsParser(is, os,
+                                mAttachment.mSize, callback);
+                        parser.parse();
+                        success = (parser.getStatusCode() == 1);
+                    } else {
+                        final int length = response.getLength();
+                        if (length != 0) {
+                            // len > 0 means that Content-Length was set in the headers
+                            // len < 0 means "chunked" transfer-encoding
+                            ItemOperationsParser.readChunked(is, os,
+                                    (length < 0) ? mAttachment.mSize : length, callback);
+                        }
+                        success = true;
+                    }
+                    // Check that we successfully grabbed what came over the wire...
+                    if (!success) {
+                        LogUtils.e(LOG_TAG, "Error parsing server response");
+                        mFinalStatus = RESULT_ATTACHMENT_RESPONSE_PARSING_ERROR;
+                        return mFinalStatus;
+                    }
+                    // Now finish the process and save to the final destination.
+                    final boolean loadResult = finishLoadAttachment(mAttachment, tmpFile);
+                    if (!loadResult) {
+                        LogUtils.e(LOG_TAG, "Error post processing attachment file.");
+                        mFinalStatus = RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
+                        return mFinalStatus;
+                    }
+                } catch (final IOException e) {
+                    LogUtils.e(LOG_TAG, "Error handling attachment: %s", e.getMessage());
+                    return RESULT_ATTACHMENT_INTERNAL_HANDLING_ERROR;
+                } finally {
+                    close(is);
+                }
+            } finally {
+                close(os);
+            }
+        } finally {
+            tmpFile.delete();
+        }
+        return mFinalStatus;
+    }
+}
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index 4007847..56a21ce 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -105,6 +105,13 @@
     /** Message MIME type for EAS version 14 and later. */
     private static final String EAS_14_MIME_TYPE = "application/vnd.ms-sync.wbxml";
 
+    /**
+     * EasOperation error codes below.  All subclasses should try to create error codes
+     * that do not overlap these codes or the codes of other subclasses. The error
+     * code values for each subclass should start in a different 100 range (i.e. -100,
+     * -200, etc...).
+     */
+
     /** 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}. */
diff --git a/src/com/android/exchange/service/EasAttachmentLoader.java b/src/com/android/exchange/service/EasAttachmentLoader.java
deleted file mode 100644
index 0f3a923..0000000
--- a/src/com/android/exchange/service/EasAttachmentLoader.java
+++ /dev/null
@@ -1,306 +0,0 @@
-package com.android.exchange.service;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-import com.android.emailcommon.provider.Account;
-import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.provider.EmailContent.Attachment;
-import com.android.emailcommon.service.EmailServiceStatus;
-import com.android.emailcommon.service.IEmailServiceCallback;
-import com.android.emailcommon.utility.AttachmentUtilities;
-import com.android.exchange.Eas;
-import com.android.exchange.EasResponse;
-import com.android.exchange.adapter.ItemOperationsParser;
-import com.android.exchange.adapter.Serializer;
-import com.android.exchange.adapter.Tags;
-import com.android.exchange.utility.UriCodec;
-import com.android.mail.utils.LogUtils;
-
-import org.apache.http.HttpStatus;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.cert.CertificateException;
-
-/**
- * Loads attachments from the Exchange server.
- * TODO: Add ability to call back to UI when this failed, and generally better handle error cases.
- */
-public class EasAttachmentLoader extends EasServerConnection {
-    private static final String TAG = Eas.LOG_TAG;
-
-    private final IEmailServiceCallback mCallback;
-
-    private EasAttachmentLoader(final Context context, final Account account,
-            final IEmailServiceCallback callback) {
-        super(context, account);
-        mCallback = callback;
-    }
-
-    // TODO: EmailServiceStatus.ATTACHMENT_NOT_FOUND is heavily used, may need to split that into
-    // different statuses.
-    private static void doStatusCallback(final IEmailServiceCallback callback,
-            final long messageKey, final long attachmentId, final int status, final int progress) {
-        if (callback != null) {
-            try {
-                callback.loadAttachmentStatus(messageKey, attachmentId, status, progress);
-            } catch (final RemoteException e) {
-                LogUtils.e(TAG, "RemoteException in loadAttachment: %s", e.getMessage());
-            }
-        }
-    }
-
-    /**
-     * Provides the parser with the data it needs to perform the callback.
-     */
-    public static class ProgressCallback {
-        private final IEmailServiceCallback mCallback;
-        private final Attachment mAttachment;
-
-        public ProgressCallback(final IEmailServiceCallback callback,
-                final Attachment attachment) {
-            mCallback = callback;
-            mAttachment = attachment;
-        }
-
-        public void doCallback(final int progress) {
-            doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachment.mId,
-                    EmailServiceStatus.IN_PROGRESS, progress);
-        }
-    }
-
-    /**
-     * Load an attachment from the Exchange server, and write it to the content provider.
-     * @param context Our {@link Context}.
-     * @param attachmentId The local id of the attachment (i.e. its id in the database).
-     * @param callback The callback for any status updates.
-     */
-    public static void loadAttachment(final Context context, final long attachmentId,
-            final IEmailServiceCallback callback) {
-        final Attachment attachment = Attachment.restoreAttachmentWithId(context, attachmentId);
-        if (attachment == null) {
-            LogUtils.d(TAG, "Could not load attachment %d", attachmentId);
-            doStatusCallback(callback, -1, attachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
-                    0);
-            return;
-        }
-        if (attachment.mLocation == null) {
-            LogUtils.e(TAG, "Attachment %d lacks a location", attachmentId);
-            doStatusCallback(callback, -1, attachmentId, EmailServiceStatus.ATTACHMENT_NOT_FOUND,
-                    0);
-            return;
-        }
-        final Account account = Account.restoreAccountWithId(context, attachment.mAccountKey);
-        if (account == null) {
-            LogUtils.d(TAG, "Attachment %d has bad account key %d", attachment.mId,
-                    attachment.mAccountKey);
-            doStatusCallback(callback, attachment.mMessageKey, attachmentId,
-                    EmailServiceStatus.ATTACHMENT_NOT_FOUND, 0);
-            return;
-        }
-        final Message message = Message.restoreMessageWithId(context, attachment.mMessageKey);
-        if (message == null) {
-            doStatusCallback(callback, attachment.mMessageKey, attachmentId,
-                EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
-            return;
-        }
-
-        // Error cases handled, do the load.
-        final EasAttachmentLoader loader =
-                new EasAttachmentLoader(context, account, callback);
-        final int status = loader.load(attachment);
-        doStatusCallback(callback, attachment.mMessageKey, attachmentId, status, 0);
-    }
-
-    /**
-     * Encoder for Exchange 2003 attachment names.  They come from the server partially encoded,
-     * but there are still possible characters that need to be encoded (Why, MSFT, why?)
-     */
-    private static class AttachmentNameEncoder extends UriCodec {
-        @Override
-        protected boolean isRetained(final char c) {
-            // These four characters are commonly received in EAS 2.5 attachment names and are
-            // valid (verified by testing); we won't encode them
-            return c == '_' || c == ':' || c == '/' || c == '.';
-        }
-    }
-
-    /**
-     * Finish encoding attachment names for Exchange 2003.
-     * @param str A partially encoded string.
-     * @return The fully encoded version of str.
-     */
-    private static String encodeForExchange2003(final String str) {
-        final AttachmentNameEncoder enc = new AttachmentNameEncoder();
-        final StringBuilder sb = new StringBuilder(str.length() + 16);
-        enc.appendPartiallyEncoded(sb, str);
-        return sb.toString();
-    }
-
-    /**
-     * Make the appropriate Exchange server request for getting the attachment.
-     * @param attachment The {@link Attachment} we wish to load.
-     * @return The {@link EasResponse} for the request, or null if we encountered an error.
-     */
-    private EasResponse performServerRequest(final Attachment attachment) {
-        try {
-            // The method of attachment loading is different in EAS 14.0 than in earlier versions
-            final String cmd;
-            final byte[] bytes;
-            if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
-                final Serializer s = new Serializer();
-                s.start(Tags.ITEMS_ITEMS).start(Tags.ITEMS_FETCH);
-                s.data(Tags.ITEMS_STORE, "Mailbox");
-                s.data(Tags.BASE_FILE_REFERENCE, attachment.mLocation);
-                s.end().end().done(); // ITEMS_FETCH, ITEMS_ITEMS
-                cmd = "ItemOperations";
-                bytes = s.toByteArray();
-            } else {
-                final String location;
-                // For Exchange 2003 (EAS 2.5), we have to look for illegal chars in the file name
-                // that EAS sent to us!
-                if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
-                    location = encodeForExchange2003(attachment.mLocation);
-                } else {
-                    location = attachment.mLocation;
-                }
-                cmd = "GetAttachment&AttachmentName=" + location;
-                bytes = null;
-            }
-            return sendHttpClientPost(cmd, bytes);
-        } catch (final IOException e) {
-            LogUtils.w(TAG, "IOException while loading attachment from server: %s", e.getMessage());
-            return null;
-        } catch (final CertificateException e) {
-            LogUtils.w(TAG, "CertificateException while loading attachment from server: %s",
-                    e.getMessage());
-            return null;
-        }
-    }
-
-    /**
-     * Close, ignoring errors (as during cleanup)
-     * @param c a Closeable
-     */
-    private static void close(final Closeable c) {
-        try {
-            c.close();
-        } catch (IOException e) {
-            LogUtils.w(TAG, "IOException while cleaning up attachment: %s", e.getMessage());
-        }
-    }
-
-    /**
-     * Save away the contentUri for this Attachment and notify listeners
-     */
-    private boolean finishLoadAttachment(final Attachment attachment, final File file) {
-        final InputStream in;
-        try {
-            in = new FileInputStream(file);
-          } catch (final FileNotFoundException e) {
-            // Unlikely, as we just created it successfully, but log it.
-            LogUtils.e(TAG, "Could not open attachment file: %s", e.getMessage());
-            return false;
-        }
-        AttachmentUtilities.saveAttachment(mContext, in, attachment);
-        close(in);
-        return true;
-    }
-
-    /**
-     * Read the {@link EasResponse} and extract the attachment data, saving it to the provider.
-     * @param resp The (successful) {@link EasResponse} containing the attachment data.
-     * @param attachment The {@link Attachment} with the attachment metadata.
-     * @return A status code, from {@link EmailServiceStatus}, for this load.
-     */
-    private int handleResponse(final EasResponse resp, final Attachment attachment) {
-        final File tmpFile;
-        try {
-            tmpFile = File.createTempFile("eas_", "tmp", mContext.getCacheDir());
-        } catch (final IOException e) {
-            LogUtils.w(TAG, "Could not open temp file: %s", e.getMessage());
-            // TODO: This is what the old implementation did, but it's kind of the wrong error.
-            return EmailServiceStatus.CONNECTION_ERROR;
-        }
-
-        try {
-            final OutputStream os;
-            try {
-                os = new FileOutputStream(tmpFile);
-            } catch (final FileNotFoundException e) {
-                LogUtils.w(TAG, "Temp file not found: %s", e.getMessage());
-                return EmailServiceStatus.ATTACHMENT_NOT_FOUND;
-            }
-            try {
-                final InputStream is = resp.getInputStream();
-                try {
-                    final ProgressCallback callback = new ProgressCallback(mCallback, attachment);
-                    final boolean success;
-                    if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
-                        final ItemOperationsParser parser = new ItemOperationsParser(is, os,
-                                attachment.mSize, callback);
-                        parser.parse();
-                        success = (parser.getStatusCode() == 1);
-                    } else {
-                        final int length = resp.getLength();
-                        if (length != 0) {
-                            // len > 0 means that Content-Length was set in the headers
-                            // len < 0 means "chunked" transfer-encoding
-                            ItemOperationsParser.readChunked(is, os,
-                                    (length < 0) ? attachment.mSize : length, callback);
-                        }
-                        success = true;
-                    }
-                    final int status;
-                    if (success && finishLoadAttachment(attachment, tmpFile)) {
-                        status = EmailServiceStatus.SUCCESS;
-                    } else {
-                        status = EmailServiceStatus.CONNECTION_ERROR;
-                    }
-                    return status;
-                } catch (final IOException e) {
-                    LogUtils.w(TAG, "Error reading attachment: %s", e.getMessage());
-                    return EmailServiceStatus.CONNECTION_ERROR;
-                } finally {
-                    close(is);
-                }
-            } finally {
-                close(os);
-            }
-        } finally {
-            tmpFile.delete();
-        }
-    }
-
-    /**
-     * Load the attachment from the server.
-     * @param attachment The attachment to load.
-     * @return A status code, from {@link EmailServiceStatus}, for this load.
-     */
-    private int load(final Attachment attachment) {
-        // Send a progress update that we're starting.
-        doStatusCallback(mCallback, attachment.mMessageKey, attachment.mId,
-                EmailServiceStatus.IN_PROGRESS, 0);
-        final EasResponse resp = performServerRequest(attachment);
-        if (resp == null) {
-            return EmailServiceStatus.CONNECTION_ERROR;
-        }
-
-        try {
-            if (resp.getStatus() != HttpStatus.SC_OK || resp.isEmpty()) {
-                return EmailServiceStatus.ATTACHMENT_NOT_FOUND;
-            }
-            return handleResponse(resp, attachment);
-        } finally {
-            resp.close();
-        }
-    }
-
-}
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index 64b932a..bf39b15 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -288,7 +288,9 @@
         post.setHeader("MS-ASProtocolVersion", String.valueOf(mProtocolVersion));
         post.setHeader("User-Agent", getUserAgent());
         post.setHeader("Accept-Encoding", "gzip");
-        if (contentType != null) {
+        // If there is no entity, we should not be setting a content-type since this will
+        // result in a 400 from the server in the case of loading an attachment.
+        if (contentType != null && entity != null) {
             post.setHeader("Content-Type", contentType);
         }
         if (usePolicyKey) {
@@ -358,8 +360,7 @@
             contentType = MimeUtility.MIME_TYPE_RFC822;
         } else if (entity != null) {
             contentType = EAS_14_MIME_TYPE;
-        }
-        else {
+        } else {
             contentType = null;
         }
         final String uriString;
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index cc4f61d..f883217 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -38,6 +38,7 @@
 import com.android.emailcommon.service.ServiceProxy;
 import com.android.exchange.Eas;
 import com.android.exchange.eas.EasFolderSync;
+import com.android.exchange.eas.EasLoadAttachment;
 import com.android.exchange.eas.EasOperation;
 import com.android.mail.utils.LogUtils;
 
@@ -73,9 +74,12 @@
         public void sendMail(final long accountId) {}
 
         @Override
-        public void loadAttachment(final IEmailServiceCallback callback, final long attachmentId,
-                final boolean background) {
+        public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
+                final long attachmentId, final boolean background) {
             LogUtils.d(TAG, "IEmailService.loadAttachment: %d", attachmentId);
+            final EasLoadAttachment operation = new EasLoadAttachment(EasService.this, accountId,
+                    attachmentId, callback);
+            doOperation(operation, null, "IEmailService.loadAttachment");
         }
 
         @Override
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 7b59985..65d9b00 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -62,6 +62,7 @@
 import com.android.exchange.adapter.PingParser;
 import com.android.exchange.adapter.Search;
 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.EasPing;
@@ -423,12 +424,13 @@
         }
 
         @Override
-        public void loadAttachment(final IEmailServiceCallback callback, final long attachmentId,
-                final boolean background) {
+        public void loadAttachment(final IEmailServiceCallback callback, final long accountId,
+                final long attachmentId, final boolean background) {
             LogUtils.d(TAG, "IEmailService.loadAttachment: %d", attachmentId);
             // TODO: Prevent this from happening in parallel with a sync?
-            EasAttachmentLoader.loadAttachment(EmailSyncAdapterService.this, attachmentId,
-                    callback);
+            final EasLoadAttachment operation = new EasLoadAttachment(EmailSyncAdapterService.this,
+                    accountId, attachmentId, callback);
+            operation.performOperation(null);
         }
 
         @Override