Home | History | Annotate | Download | only in eas
      1 package com.android.exchange.eas;
      2 
      3 import android.content.ContentResolver;
      4 import android.content.ContentUris;
      5 import android.content.ContentValues;
      6 import android.content.Context;
      7 import android.content.SyncResult;
      8 
      9 import com.android.emailcommon.provider.Account;
     10 import com.android.emailcommon.provider.EmailContent;
     11 import com.android.emailcommon.provider.MessageMove;
     12 import com.android.exchange.EasResponse;
     13 import com.android.exchange.adapter.MoveItemsParser;
     14 import com.android.exchange.adapter.Serializer;
     15 import com.android.exchange.adapter.Tags;
     16 import com.android.mail.utils.LogUtils;
     17 
     18 import org.apache.http.HttpEntity;
     19 
     20 import java.io.IOException;
     21 import java.util.List;
     22 
     23 /**
     24  * Performs a MoveItems request, which is used to move items between collections.
     25  * See http://msdn.microsoft.com/en-us/library/ee160102(v=exchg.80).aspx for more details.
     26  * TODO: Investigate how this interacts with ItemOperations.
     27  */
     28 public class EasMoveItems extends EasOperation {
     29 
     30     /** Result code indicating that no moved messages were found for this account. */
     31     public final static int RESULT_NO_MESSAGES = 0;
     32     public final static int RESULT_OK = 1;
     33     public final static int RESULT_EMPTY_RESPONSE = 2;
     34 
     35     private static class MoveResponse {
     36         public final String sourceMessageId;
     37         public final String newMessageId;
     38         public final int moveStatus;
     39 
     40         public MoveResponse(final String srcMsgId, final String dstMsgId, final int status) {
     41             sourceMessageId = srcMsgId;
     42             newMessageId = dstMsgId;
     43             moveStatus = status;
     44         }
     45     }
     46 
     47     private MessageMove mMove;
     48     private MoveResponse mResponse;
     49 
     50     public EasMoveItems(final Context context, final Account account) {
     51         super(context, account);
     52     }
     53 
     54     // TODO: Allow multiple messages in one request. Requires parser changes.
     55     public int upsyncMovedMessages(final SyncResult syncResult) {
     56         final List<MessageMove> moves = MessageMove.getMoves(mContext, mAccountId);
     57         if (moves == null) {
     58             return RESULT_NO_MESSAGES;
     59         }
     60 
     61         final long[][] messageIds = new long[3][moves.size()];
     62         final int[] counts = new int[3];
     63 
     64         for (final MessageMove move : moves) {
     65             mMove = move;
     66             final int result = performOperation(syncResult);
     67             final int status;
     68             if (result == RESULT_OK) {
     69                 processResponse(mMove, mResponse);
     70                 status = mResponse.moveStatus;
     71             } else {
     72                 // TODO: Perhaps not all errors should be retried?
     73                 // Notably, if the server returns 200 with an empty response, we retry. This is
     74                 // how the previous version worked, and I can't find documentation about what this
     75                 // response state really means.
     76                 status = MoveItemsParser.STATUS_CODE_RETRY;
     77             }
     78             final int index;
     79             if (status <= 0) {
     80                 LogUtils.e(LOG_TAG, "MoveItems gave us an invalid status %d", status);
     81                 index = MoveItemsParser.STATUS_CODE_RETRY - 1;
     82             } else {
     83                 index = status - 1;
     84             }
     85             messageIds[index][counts[index]] = mMove.getMessageId();
     86             ++counts[index];
     87         }
     88 
     89         final ContentResolver cr = mContext.getContentResolver();
     90         MessageMove.upsyncSuccessful(cr, messageIds[0], counts[0]);
     91         MessageMove.upsyncFail(cr, messageIds[1], counts[1]);
     92         MessageMove.upsyncRetry(cr, messageIds[2], counts[2]);
     93 
     94         return RESULT_OK;
     95     }
     96 
     97     @Override
     98     protected String getCommand() {
     99         return "MoveItems";
    100     }
    101 
    102     @Override
    103     protected HttpEntity getRequestEntity() throws IOException {
    104         final Serializer s = new Serializer();
    105         s.start(Tags.MOVE_MOVE_ITEMS);
    106         s.start(Tags.MOVE_MOVE);
    107         s.data(Tags.MOVE_SRCMSGID, mMove.getServerId());
    108         s.data(Tags.MOVE_SRCFLDID, mMove.getSourceFolderId());
    109         s.data(Tags.MOVE_DSTFLDID, mMove.getDestFolderId());
    110         s.end();
    111         s.end().done();
    112         return makeEntity(s);
    113     }
    114 
    115     @Override
    116     protected int handleResponse(final EasResponse response, final SyncResult syncResult)
    117             throws IOException {
    118         if (!response.isEmpty()) {
    119             final MoveItemsParser parser = new MoveItemsParser(response.getInputStream());
    120             parser.parse();
    121             final String sourceMessageId = parser.getSourceServerId();
    122             final String newMessageId = parser.getNewServerId();
    123             final int status = parser.getStatusCode();
    124             mResponse = new MoveResponse(sourceMessageId, newMessageId, status);
    125             return RESULT_OK;
    126         }
    127         return RESULT_EMPTY_RESPONSE;
    128     }
    129 
    130     private void processResponse(final MessageMove request, final MoveResponse response) {
    131         // TODO: Eventually this should use a transaction.
    132         // TODO: Improve how the parser reports statuses and how we handle them here.
    133 
    134         final String sourceMessageId;
    135 
    136         if (response.sourceMessageId == null) {
    137             // The response didn't contain SrcMsgId, despite it being required.
    138             LogUtils.e(LOG_TAG,
    139                     "MoveItems response for message %d has no SrcMsgId, using request's server id",
    140                     request.getMessageId());
    141             sourceMessageId = request.getServerId();
    142         } else {
    143             sourceMessageId = response.sourceMessageId;
    144             if (!sourceMessageId.equals(request.getServerId())) {
    145                 // TODO: This is bad, but we still need to process the response. Just log for now.
    146                 LogUtils.e(LOG_TAG,
    147                         "MoveItems response for message %d has SrcMsgId != request's server id",
    148                         request.getMessageId());
    149             }
    150         }
    151 
    152         final ContentValues cv = new ContentValues(1);
    153         if (response.moveStatus == MoveItemsParser.STATUS_CODE_REVERT) {
    154             // Restore the old mailbox id
    155             cv.put(EmailContent.MessageColumns.MAILBOX_KEY, request.getSourceFolderKey());
    156         } else if (response.moveStatus == MoveItemsParser.STATUS_CODE_SUCCESS) {
    157             if (response.newMessageId != null && !response.newMessageId.equals(sourceMessageId)) {
    158                 cv.put(EmailContent.SyncColumns.SERVER_ID, response.newMessageId);
    159             }
    160         }
    161         if (cv.size() != 0) {
    162             mContext.getContentResolver().update(
    163                     ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
    164                             request.getMessageId()), cv, null, null);
    165         }
    166     }
    167 }
    168