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