From 6f0cf29be849d3a95a9168cb6fa09de12635e818 Mon Sep 17 00:00:00 2001 From: Yu Ping Hu Date: Fri, 23 Aug 2013 09:39:58 -0700 Subject: Add MoveItems operation. Change-Id: I39d7aeb2de4a01ec9237f574b752b57662829eb4 --- Android.mk | 1 + .../android/exchange/adapter/MoveItemsParser.java | 8 ++ src/com/android/exchange/eas/EasMoveItems.java | 143 +++++++++++++++++++++ src/com/android/exchange/eas/EasOperation.java | 8 +- src/com/android/exchange/eas/EasSync.java | 9 +- .../exchange/service/EmailSyncAdapterService.java | 4 + 6 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 src/com/android/exchange/eas/EasMoveItems.java diff --git a/Android.mk b/Android.mk index 0551fe8f..2cbb4788 100644 --- a/Android.mk +++ b/Android.mk @@ -34,6 +34,7 @@ LOCAL_SRC_FILES += $(call all-java-files-under, build/src) LOCAL_STATIC_JAVA_LIBRARIES := android-common com.android.emailcommon com.android.emailsync LOCAL_STATIC_JAVA_LIBRARIES += calendar-common +LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 LOCAL_PACKAGE_NAME := Exchange2 LOCAL_OVERRIDES_PACKAGES := Exchange diff --git a/src/com/android/exchange/adapter/MoveItemsParser.java b/src/com/android/exchange/adapter/MoveItemsParser.java index a654c76c..be11727f 100644 --- a/src/com/android/exchange/adapter/MoveItemsParser.java +++ b/src/com/android/exchange/adapter/MoveItemsParser.java @@ -27,6 +27,7 @@ public class MoveItemsParser extends Parser { private static final String TAG = "MoveItemsParser"; private int mStatusCode = 0; private String mNewServerId; + private String mSourceServerId; // These are the EAS status codes for MoveItems private static final int STATUS_NO_SOURCE_FOLDER = 1; @@ -54,6 +55,10 @@ public class MoveItemsParser extends Parser { return mNewServerId; } + public String getSourceServerId() { + return mSourceServerId; + } + public void parseResponse() throws IOException { while (nextTag(Tags.MOVE_RESPONSE) != END) { if (tag == Tags.MOVE_STATUS) { @@ -87,6 +92,9 @@ public class MoveItemsParser extends Parser { } else if (tag == Tags.MOVE_DSTMSGID) { mNewServerId = getValue(); LogUtils.i(TAG, "Moved message id is now: %s", mNewServerId); + } else if (tag == Tags.MOVE_SRCMSGID) { + mSourceServerId = getValue(); + LogUtils.i(TAG, "Source message id is: %s", mNewServerId); } else { skipTag(); } diff --git a/src/com/android/exchange/eas/EasMoveItems.java b/src/com/android/exchange/eas/EasMoveItems.java new file mode 100644 index 00000000..a9c5c3fc --- /dev/null +++ b/src/com/android/exchange/eas/EasMoveItems.java @@ -0,0 +1,143 @@ +package com.android.exchange.eas; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.SyncResult; + +import com.android.emailcommon.provider.Account; +import com.android.emailcommon.provider.EmailContent; +import com.android.emailcommon.provider.MessageMove; +import com.android.exchange.EasResponse; +import com.android.exchange.adapter.MoveItemsParser; +import com.android.exchange.adapter.Serializer; +import com.android.exchange.adapter.Tags; +import com.android.mail.utils.LogUtils; + +import org.apache.http.HttpEntity; + +import java.io.IOException; +import java.util.List; + +/** + * Performs a MoveItems request, which is used to move items between collections. + * See http://msdn.microsoft.com/en-us/library/ee160102(v=exchg.80).aspx for more details. + * TODO: Investigate how this interacts with ItemOperations. + */ +public class EasMoveItems extends EasOperation { + + /** Result code indicating that no moved messages were found for this account. */ + public final static int RESULT_NO_MESSAGES = 0; + public final static int RESULT_OK = 1; + + private static class MoveResponse { + public final String sourceMessageId; + public final String newMessageId; + public final int moveStatus; + + public MoveResponse(final String srcMsgId, final String dstMsgId, final int status) { + sourceMessageId = srcMsgId; + newMessageId = dstMsgId; + moveStatus = status; + } + } + + private MessageMove mMove; + private MoveResponse mResponse; + + public EasMoveItems(final Context context, final Account account) { + super(context, account); + } + + // TODO: Allow multiple messages in one request. Requires parser changes. + public int upsyncMovedMessages(final SyncResult syncResult) { + final List moves = MessageMove.getMoves(mContext, mAccountId); + if (moves == null) { + return RESULT_NO_MESSAGES; + } + + final long[][] messageIds = new long[3][moves.size()]; + final int[] counts = new int[3]; + + for (final MessageMove move : moves) { + mMove = move; + final int result = performOperation(syncResult); + final int status; + if (result == RESULT_OK) { + processResponse(mMove, mResponse); + status = mResponse.moveStatus; + } else { + // TODO: Perhaps not all errors should be retried? + status = MoveItemsParser.STATUS_CODE_RETRY; + } + final int index = status - 1; + messageIds[index][counts[index]] = mMove.getMessageId(); + ++counts[index]; + } + + final ContentResolver cr = mContext.getContentResolver(); + MessageMove.upsyncSuccessful(cr, messageIds[0], counts[0]); + MessageMove.upsyncFail(cr, messageIds[1], counts[1]); + MessageMove.upsyncRetry(cr, messageIds[2], counts[2]); + + return RESULT_OK; + } + + @Override + protected String getCommand() { + return "MoveItems"; + } + + @Override + protected HttpEntity getRequestEntity() throws IOException { + final Serializer s = new Serializer(); + s.start(Tags.MOVE_MOVE_ITEMS); + s.start(Tags.MOVE_MOVE); + s.data(Tags.MOVE_SRCMSGID, mMove.getServerId()); + s.data(Tags.MOVE_SRCFLDID, mMove.getSourceFolderId()); + s.data(Tags.MOVE_DSTFLDID, mMove.getDestFolderId()); + s.end(); + s.end().done(); + return makeEntity(s); + } + + @Override + protected int handleResponse(final EasResponse response, final SyncResult syncResult) + throws IOException { + if (!response.isEmpty()) { + final MoveItemsParser parser = new MoveItemsParser(response.getInputStream()); + parser.parse(); + final String sourceMessageId = parser.getSourceServerId(); + final String newMessageId = parser.getNewServerId(); + final int status = parser.getStatusCode(); + mResponse = new MoveResponse(sourceMessageId, newMessageId, status); + } + return RESULT_OK; + } + + private void processResponse(final MessageMove request, final MoveResponse response) { + // TODO: Eventually this should use a transaction. + // TODO: Improve how the parser reports statuses and how we handle them here. + if (!response.sourceMessageId.equals(request.getServerId())) { + // TODO: This is bad, but I think we need to respect the response anyway. + LogUtils.e(LOG_TAG, "Got a response for a message we didn't request"); + } + + final ContentValues cv = new ContentValues(1); + if (response.moveStatus == MoveItemsParser.STATUS_CODE_REVERT) { + // Restore the old mailbox id + cv.put(EmailContent.MessageColumns.MAILBOX_KEY, request.getSourceFolderKey()); + } else if (response.moveStatus == MoveItemsParser.STATUS_CODE_SUCCESS) { + if (response.newMessageId != null + && !response.newMessageId.equals(response.sourceMessageId)) { + cv.put(EmailContent.SyncColumns.SERVER_ID, response.newMessageId); + } + } + if (cv.size() != 0) { + mContext.getContentResolver().update( + ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, + request.getMessageId()), cv, null, null); + } + } +} diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java index 1485e788..df311cd5 100644 --- a/src/com/android/exchange/eas/EasOperation.java +++ b/src/com/android/exchange/eas/EasOperation.java @@ -95,10 +95,10 @@ public abstract class EasOperation { /** * The account id for this operation. - * Currently only used for handling provisioning errors. Ideally we should minimize the creep - * of how this gets used (i.e. don't let it get to the intertwined state of the past). + * NOTE: You will be tempted to add a reference to the {@link Account} here. Resist. + * It's too easy for that to lead to creep and stale data. */ - private final long mAccountId; + protected final long mAccountId; private final EasServerConnection mConnection; private EasOperation(final Context context, final long accountId, @@ -190,7 +190,7 @@ public abstract class EasOperation { return RESULT_REQUEST_FAILURE; } catch (final IllegalStateException e) { // Subclasses use ISE to signal a hard error when building the request. - // TODO: If executeHttpUriRequest can throw an ISE, we may want to tidy this up. + // TODO: Switch away from ISEs. LogUtils.e(LOG_TAG, e, "Exception while sending request"); if (syncResult != null) { syncResult.databaseError = true; diff --git a/src/com/android/exchange/eas/EasSync.java b/src/com/android/exchange/eas/EasSync.java index 34dee034..03c614b6 100644 --- a/src/com/android/exchange/eas/EasSync.java +++ b/src/com/android/exchange/eas/EasSync.java @@ -56,7 +56,12 @@ public class EasSync extends EasOperation { @Override protected HttpEntity getRequestEntity() throws IOException { - return null; + final Serializer s = new Serializer(); + s.start(Tags.SYNC_SYNC); + s.start(Tags.SYNC_COLLECTIONS); + addOneCollectionToRequest(s, mMailbox); + s.end().end().done(); + return makeEntity(s); } @@ -80,6 +85,8 @@ public class EasSync extends EasOperation { if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) { s.data(Tags.SYNC_CLASS, Eas.getFolderClass(mailbox.mType)); } + s.data(Tags.SYNC_SYNC_KEY, getSyncKeyForMailbox(mailbox)); + s.data(Tags.SYNC_COLLECTION_ID, mailbox.mServerId); s.end(); } diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java index b2a50554..b60cd522 100644 --- a/src/com/android/exchange/service/EmailSyncAdapterService.java +++ b/src/com/android/exchange/service/EmailSyncAdapterService.java @@ -43,6 +43,7 @@ import com.android.exchange.Eas; import com.android.exchange.adapter.PingParser; import com.android.exchange.adapter.Search; import com.android.exchange.eas.EasFolderSync; +import com.android.exchange.eas.EasMoveItems; import com.android.exchange.eas.EasOperation; import com.android.exchange.eas.EasPing; import com.android.mail.providers.UIProvider.AccountCapabilities; @@ -528,6 +529,9 @@ public class EmailSyncAdapterService extends AbstractSyncAdapterService { // Do the bookkeeping for starting a sync, including stopping a ping if necessary. mSyncHandlerMap.startSync(account.mId); + EasMoveItems move = new EasMoveItems(context, account); + move.upsyncMovedMessages(syncResult); + // TODO: Should we refresh the account here? It may have changed while waiting for any // pings to stop. It may not matter since the things that may have been twiddled might // not affect syncing. -- cgit v1.2.3