diff options
author | Anthony Lee <anthonylee@google.com> | 2014-02-26 08:55:37 -0800 |
---|---|---|
committer | Yu Ping Hu <yph@google.com> | 2014-03-03 19:24:17 +0000 |
commit | 02d8984e7452e6c3a3aaec189aca885c3f206a32 (patch) | |
tree | ac701d35d4eb090b9954e8548640a4fc774092c7 | |
parent | b6480e9e43695cf8dc9ba8981612fea8d032fb11 (diff) | |
download | Exchange-02d8984e7452e6c3a3aaec189aca885c3f206a32.tar.gz |
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)
8 files changed, 387 insertions, 318 deletions
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java index 85ea33fc..eef2ce6a 100644 --- a/src/com/android/exchange/ExchangeService.java +++ b/src/com/android/exchange/ExchangeService.java @@ -132,8 +132,8 @@ public class ExchangeService extends SyncManager { } @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 b3839937..deace34a 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 00000000..fe51a967 --- /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 40078476..56a21cec 100644 --- a/src/com/android/exchange/eas/EasOperation.java +++ b/src/com/android/exchange/eas/EasOperation.java @@ -105,6 +105,13 @@ public abstract class EasOperation { /** 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 0f3a9230..00000000 --- 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 64b932a0..bf39b15e 100644 --- a/src/com/android/exchange/service/EasServerConnection.java +++ b/src/com/android/exchange/service/EasServerConnection.java @@ -288,7 +288,9 @@ public class EasServerConnection { 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 @@ public class EasServerConnection { 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 82d8318e..b5081f60 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.SearchParams; 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 class EasService extends Service { 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 7b599856..65d9b005 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.R.string; 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 @@ public class EmailSyncAdapterService extends AbstractSyncAdapterService { } @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 |