summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Lee <anthonylee@google.com>2014-02-26 08:55:37 -0800
committerYu Ping Hu <yph@google.com>2014-03-03 19:24:17 +0000
commit02d8984e7452e6c3a3aaec189aca885c3f206a32 (patch)
treeac701d35d4eb090b9954e8548640a4fc774092c7
parentb6480e9e43695cf8dc9ba8981612fea8d032fb11 (diff)
downloadExchange-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)
-rw-r--r--src/com/android/exchange/ExchangeService.java4
-rw-r--r--src/com/android/exchange/adapter/ItemOperationsParser.java2
-rw-r--r--src/com/android/exchange/eas/EasLoadAttachment.java361
-rw-r--r--src/com/android/exchange/eas/EasOperation.java7
-rw-r--r--src/com/android/exchange/service/EasAttachmentLoader.java306
-rw-r--r--src/com/android/exchange/service/EasServerConnection.java7
-rw-r--r--src/com/android/exchange/service/EasService.java8
-rw-r--r--src/com/android/exchange/service/EmailSyncAdapterService.java10
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