Fix regular sync, part 1.
- Add support for new IEmailService#sync.
- Don't pass SyncResult through EasOperations. This was
probably a bad idea to begin with. Instead, callers that
care should use performOperation()'s return value to set
the results appropriately.
- Fix upsync operations to signal request/network errors
ASAP (and mark any unperformed upsyncs as retries).
- In ESAS, bail early on first error.
- In ESAS, set up forwarding to EasService for pushModify.
- Converting mail downsync into an EasOperation. For now,
not merging it with the EasSync class that already exists.
Change-Id: I4719c5cafbd01a957f267b221cf3276010154176
diff --git a/src/com/android/exchange/ExchangeService.java b/src/com/android/exchange/ExchangeService.java
index eef2ce6..d0b9a6c 100644
--- a/src/com/android/exchange/ExchangeService.java
+++ b/src/com/android/exchange/ExchangeService.java
@@ -177,12 +177,14 @@
}
@Override
- public void sendMail(long accountId) throws RemoteException {
- }
+ public void sendMail(long accountId) throws RemoteException {}
@Override
- public void pushModify(long accountId) throws RemoteException {
- }
+ public void pushModify(long accountId) throws RemoteException {}
+
+ @Override
+ public void sync(final long accountId, final boolean updateFolderList,
+ final int mailboxType, final long[] folders) {}
};
/**
diff --git a/src/com/android/exchange/eas/EasFolderSync.java b/src/com/android/exchange/eas/EasFolderSync.java
index 48d4070..34da843 100644
--- a/src/com/android/exchange/eas/EasFolderSync.java
+++ b/src/com/android/exchange/eas/EasFolderSync.java
@@ -17,7 +17,6 @@
package com.android.exchange.eas;
import android.content.Context;
-import android.content.SyncResult;
import android.os.Bundle;
import com.android.emailcommon.mail.MessagingException;
@@ -99,12 +98,12 @@
}
@Override
- public int performOperation(final SyncResult syncResult) {
+ public int performOperation() {
if (mStatusOnly) {
return validate();
} else {
LogUtils.d(LOG_TAG, "Performing FolderSync for account %d", getAccountId());
- return super.performOperation(syncResult);
+ return super.performOperation();
}
}
@@ -119,10 +118,9 @@
/**
* Perform a folder sync.
* TODO: Remove this function when transition to EasService is complete.
- * @param syncResult The {@link SyncResult} object for this sync operation.
* @return A result code, either from above or from the base class.
*/
- public int doFolderSync(final SyncResult syncResult) {
+ public int doFolderSync() {
if (mStatusOnly) {
return RESULT_WRONG_OPERATION;
}
@@ -130,7 +128,7 @@
// This intentionally calls super.performOperation -- calling our performOperation
// will simply end up calling super.performOperation anyway. This is part of the transition
// to EasService and will go away when this function is deleted.
- return super.performOperation(syncResult);
+ return super.performOperation();
}
/**
@@ -155,7 +153,7 @@
if (shouldGetProtocolVersion()) {
final EasOptions options = new EasOptions(this);
- final int result = options.getProtocolVersionFromServer(null);
+ final int result = options.getProtocolVersionFromServer();
if (result != EasOptions.RESULT_OK) {
writeResultCode(mValidationResult, result);
return result;
@@ -168,7 +166,7 @@
// This is intentionally a call to super.performOperation. This is a helper function for
// our version of perfomOperation so calling that function would infinite loop.
- final int result = super.performOperation(null);
+ final int result = super.performOperation();
writeResultCode(mValidationResult, result);
return result;
}
@@ -198,7 +196,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
+ protected int handleResponse(final EasResponse response)
throws IOException, CommandStatusException {
if (!response.isEmpty()) {
new FolderSyncParser(mContext, mContext.getContentResolver(),
@@ -213,7 +211,7 @@
}
@Override
- protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
+ protected boolean handleProvisionError() {
if (mStatusOnly) {
final EasProvision provisionOperation = new EasProvision(this);
mPolicy = provisionOperation.test();
@@ -221,7 +219,7 @@
// no need to re-run the operation.
return false;
}
- return super.handleProvisionError(syncResult, accountId);
+ return super.handleProvisionError();
}
/**
diff --git a/src/com/android/exchange/eas/EasLoadAttachment.java b/src/com/android/exchange/eas/EasLoadAttachment.java
index fe51a96..260d3f8 100644
--- a/src/com/android/exchange/eas/EasLoadAttachment.java
+++ b/src/com/android/exchange/eas/EasLoadAttachment.java
@@ -17,7 +17,6 @@
package com.android.exchange.eas;
import android.content.Context;
-import android.content.SyncResult;
import android.os.RemoteException;
import com.android.emailcommon.provider.EmailContent;
@@ -25,7 +24,6 @@
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;
@@ -147,11 +145,10 @@
/**
* 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) {
+ public int performOperation() {
mAttachment = EmailContent.Attachment.restoreAttachmentWithId(mContext, mAttachmentId);
if (mAttachment == null) {
LogUtils.e(LOG_TAG, "Could not load attachment %d", mAttachmentId);
@@ -178,7 +175,7 @@
doStatusCallback(mCallback, mAttachment.mMessageKey, mAttachment.mId,
EmailServiceStatus.IN_PROGRESS, 0);
- final int return_value = super.performOperation(syncResult);
+ final int return_value = super.performOperation();
// 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
@@ -270,12 +267,10 @@
/**
* 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 {
+ protected int handleResponse(final EasResponse response) {
// 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
diff --git a/src/com/android/exchange/eas/EasMoveItems.java b/src/com/android/exchange/eas/EasMoveItems.java
index ed2ecd7..a57b95d 100644
--- a/src/com/android/exchange/eas/EasMoveItems.java
+++ b/src/com/android/exchange/eas/EasMoveItems.java
@@ -4,7 +4,6 @@
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;
@@ -52,7 +51,7 @@
}
// TODO: Allow multiple messages in one request. Requires parser changes.
- public int upsyncMovedMessages(final SyncResult syncResult) {
+ public int upsyncMovedMessages() {
final List<MessageMove> moves = MessageMove.getMoves(mContext, getAccountId());
if (moves == null) {
return RESULT_NO_MESSAGES;
@@ -60,19 +59,31 @@
final long[][] messageIds = new long[3][moves.size()];
final int[] counts = new int[3];
+ int result = RESULT_NO_MESSAGES;
for (final MessageMove move : moves) {
mMove = move;
- final int result = performOperation(syncResult);
+ if (result >= 0) {
+ // If our previous time through the loop succeeded, keep making server requests.
+ // Otherwise, we carry through the loop for all messages with the last error
+ // response, which will stop trying this iteration and force the rest of the
+ // messages into the retry state.
+ result = performOperation();
+ }
final int status;
- if (result == RESULT_OK) {
- processResponse(mMove, mResponse);
- status = mResponse.moveStatus;
+ if (result >= 0) {
+ if (result == RESULT_OK) {
+ processResponse(mMove, mResponse);
+ status = mResponse.moveStatus;
+ } else {
+ // TODO: Should this really be a retry?
+ // We got a 200 response with an empty payload. It's not clear we ought to
+ // retry, but this is how our implementation has worked in the past.
+ status = MoveItemsParser.STATUS_CODE_RETRY;
+ }
} else {
- // TODO: Perhaps not all errors should be retried?
- // Notably, if the server returns 200 with an empty response, we retry. This is
- // how the previous version worked, and I can't find documentation about what this
- // response state really means.
+ // performOperation returned a negative status code, indicating a failure before the
+ // server actually was able to tell us yea or nay, so we must retry.
status = MoveItemsParser.STATUS_CODE_RETRY;
}
final int index;
@@ -91,7 +102,10 @@
MessageMove.upsyncFail(cr, messageIds[1], counts[1]);
MessageMove.upsyncRetry(cr, messageIds[2], counts[2]);
- return RESULT_OK;
+ if (result >= 0) {
+ return RESULT_OK;
+ }
+ return result;
}
@Override
@@ -113,8 +127,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
- throws IOException {
+ protected int handleResponse(final EasResponse response) throws IOException {
if (!response.isEmpty()) {
final MoveItemsParser parser = new MoveItemsParser(response.getInputStream());
parser.parse();
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index 56a21ce..07fa1f9 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -132,8 +132,10 @@
public static final int RESULT_PROTOCOL_VERSION_UNSUPPORTED = -9;
/** Error code indicating the account could not be loaded from the provider. */
public static final int RESULT_ACCOUNT_ID_INVALID = -10;
+ /** Error code indicating a hard data layer error. */
+ public static final int RESULT_HARD_DATA_FAILURE = -11;
/** Error code indicating some other failure. */
- public static final int RESULT_OTHER_FAILURE = -11;
+ public static final int RESULT_OTHER_FAILURE = -12;
protected final Context mContext;
@@ -254,11 +256,9 @@
* negative result code, which will be handled the same as if it had been indicated in the HTTP
* response code.
*
- * @param syncResult If this operation is a sync, the {@link SyncResult} object that should
- * be written to for this sync; otherwise null.
* @return A result code for the outcome of this operation, as described above.
*/
- public int performOperation(final SyncResult syncResult) {
+ public int performOperation() {
// Make sure the account is loaded if it hasn't already been.
if (!loadAccount(false)) {
LogUtils.i(LOG_TAG, "Failed to load account %d before sending request for operation %s",
@@ -290,26 +290,16 @@
message = "(no message)";
}
LogUtils.i(LOG_TAG, "IOException while sending request: %s", message);
- if (syncResult != null) {
- ++syncResult.stats.numIoExceptions;
- }
return RESULT_REQUEST_FAILURE;
} catch (final CertificateException e) {
LogUtils.i(LOG_TAG, "CertificateException while sending request: %s",
e.getMessage());
- if (syncResult != null) {
- // TODO: Is this the best stat to increment?
- ++syncResult.stats.numAuthExceptions;
- }
return RESULT_CLIENT_CERTIFICATE_REQUIRED;
} catch (final IllegalStateException e) {
// Subclasses use ISE to signal a hard error when building the request.
// TODO: Switch away from ISEs.
LogUtils.e(LOG_TAG, e, "Exception while sending request");
- if (syncResult != null) {
- syncResult.databaseError = true;
- }
- return RESULT_OTHER_FAILURE;
+ return RESULT_HARD_DATA_FAILURE;
}
// The POST completed, so process the response.
@@ -319,12 +309,9 @@
if (response.isSuccess()) {
int responseResult;
try {
- responseResult = handleResponse(response, syncResult);
+ responseResult = handleResponse(response);
} catch (final IOException e) {
LogUtils.e(LOG_TAG, e, "Exception while handling response");
- if (syncResult != null) {
- ++syncResult.stats.numIoExceptions;
- }
return RESULT_REQUEST_FAILURE;
} catch (final CommandStatusException e) {
// For some operations (notably Sync & FolderSync), errors are signaled in
@@ -356,36 +343,24 @@
// If this operation has distinct handling for 403 errors, do that.
if (result == RESULT_FORBIDDEN || (response.isForbidden() && handleForbidden())) {
LogUtils.e(LOG_TAG, "Forbidden response");
- if (syncResult != null) {
- // TODO: Is this the best stat to increment?
- ++syncResult.stats.numAuthExceptions;
- }
return RESULT_FORBIDDEN;
}
// Handle provisioning errors.
if (result == RESULT_PROVISIONING_ERROR || response.isProvisionError()) {
- if (handleProvisionError(syncResult, getAccountId())) {
+ if (handleProvisionError()) {
// The provisioning error has been taken care of, so we should re-do this
// request.
LogUtils.d(LOG_TAG, "Provisioning error handled during %s, retrying",
getCommand());
continue;
}
- if (syncResult != null) {
- LogUtils.e(LOG_TAG, "Issue with provisioning");
- // TODO: Is this the best stat to increment?
- ++syncResult.stats.numAuthExceptions;
- }
return RESULT_PROVISIONING_ERROR;
}
// Handle authentication errors.
if (response.isAuthError()) {
LogUtils.e(LOG_TAG, "Authentication error");
- if (syncResult != null) {
- ++syncResult.stats.numAuthExceptions;
- }
if (response.isMissingCertificate()) {
return RESULT_CLIENT_CERTIFICATE_REQUIRED;
}
@@ -401,10 +376,6 @@
// All other errors.
LogUtils.e(LOG_TAG, "Generic error for operation %s: status %d, result %d",
getCommand(), response.getStatus(), result);
- if (syncResult != null) {
- // TODO: Is this the best stat to increment?
- ++syncResult.stats.numIoExceptions;
- }
return RESULT_OTHER_FAILURE;
}
} finally {
@@ -415,9 +386,6 @@
// Non-redirects return immediately after handling, so the only way to reach here is if we
// looped too many times.
LogUtils.e(LOG_TAG, "Too many redirects");
- if (syncResult != null) {
- syncResult.tooManyRetries = true;
- }
return RESULT_TOO_MANY_REDIRECTS;
}
@@ -487,15 +455,13 @@
/**
* Parse the response from the Exchange perform whatever actions are dictated by that.
* @param response The {@link EasResponse} to our request.
- * @param syncResult The {@link SyncResult} object for this operation, or null if we're not
- * handling a sync.
* @return A result code. Non-negative values are returned directly to the caller; negative
* values
*
* that is returned to the caller of {@link #performOperation}.
* @throws IOException
*/
- protected abstract int handleResponse(final EasResponse response, final SyncResult syncResult)
+ protected abstract int handleResponse(final EasResponse response)
throws IOException, CommandStatusException;
/**
@@ -545,13 +511,11 @@
/**
* Handle a provisioning error. Subclasses may override this to do something different, e.g.
* to validate rather than actually do the provisioning.
- * @param syncResult
- * @param accountId
* @return
*/
- protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
+ protected boolean handleProvisionError() {
final EasProvision provisionOperation = new EasProvision(this);
- return provisionOperation.provision(syncResult, accountId);
+ return provisionOperation.provision();
}
/**
@@ -734,4 +698,32 @@
LogUtils.d(LOG_TAG, "requestSync EasOperation requestNoOpSync %s, %s",
amAccount.toString(), extras.toString());
}
+
+ public static void writeResultToSyncResult(final int result, final SyncResult syncResult) {
+ switch (result) {
+ case RESULT_TOO_MANY_REDIRECTS:
+ syncResult.tooManyRetries = true;
+ break;
+ case RESULT_REQUEST_FAILURE:
+ syncResult.stats.numIoExceptions = 1;
+ break;
+ case RESULT_FORBIDDEN:
+ case RESULT_PROVISIONING_ERROR:
+ case RESULT_AUTHENTICATION_ERROR:
+ case RESULT_CLIENT_CERTIFICATE_REQUIRED:
+ syncResult.stats.numAuthExceptions = 1;
+ break;
+ case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
+ // Only used in validate, so there's never a syncResult to write to here.
+ break;
+ case RESULT_ACCOUNT_ID_INVALID:
+ case RESULT_HARD_DATA_FAILURE:
+ syncResult.databaseError = true;
+ break;
+ case RESULT_OTHER_FAILURE:
+ // TODO: Is this correct?
+ syncResult.stats.numIoExceptions = 1;
+ break;
+ }
+ }
}
diff --git a/src/com/android/exchange/eas/EasOptions.java b/src/com/android/exchange/eas/EasOptions.java
index 32d2c75..131c391 100644
--- a/src/com/android/exchange/eas/EasOptions.java
+++ b/src/com/android/exchange/eas/EasOptions.java
@@ -16,8 +16,6 @@
package com.android.exchange.eas;
-import android.content.SyncResult;
-
import com.android.exchange.Eas;
import com.android.exchange.EasResponse;
import com.android.mail.utils.LogUtils;
@@ -53,11 +51,10 @@
/**
* Perform the server request. If successful, callers should use
* {@link #getProtocolVersionString} to get the actual protocol version value.
- * @param syncResult The {@link SyncResult} to use for this operation.
* @return A result code; {@link #RESULT_OK} is the only value that indicates success.
*/
- public int getProtocolVersionFromServer(final SyncResult syncResult) {
- return performOperation(syncResult);
+ public int getProtocolVersionFromServer() {
+ return performOperation();
}
/**
@@ -82,7 +79,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult) {
+ protected int handleResponse(final EasResponse response) {
final Header commands = response.getHeader("MS-ASProtocolCommands");
final Header versions = response.getHeader("ms-asprotocolversions");
final boolean hasProtocolVersion;
diff --git a/src/com/android/exchange/eas/EasPing.java b/src/com/android/exchange/eas/EasPing.java
index d8e28a4..be1a679 100644
--- a/src/com/android/exchange/eas/EasPing.java
+++ b/src/com/android/exchange/eas/EasPing.java
@@ -19,7 +19,6 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
-import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.os.SystemClock;
@@ -102,7 +101,7 @@
public final int doPing() {
final long startTime = SystemClock.elapsedRealtime();
- final int result = performOperation(null);
+ final int result = performOperation();
if (result == RESULT_RESTART) {
return PingParser.STATUS_EXPIRED;
} else if (result == RESULT_REQUEST_FAILURE) {
@@ -176,8 +175,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
- throws IOException {
+ protected int handleResponse(final EasResponse response) throws IOException {
if (response.isEmpty()) {
// TODO this should probably not be an IOException, maybe something more descriptive?
throw new IOException("Empty ping response");
diff --git a/src/com/android/exchange/eas/EasProvision.java b/src/com/android/exchange/eas/EasProvision.java
index 7b5bcb9..4ea003d 100644
--- a/src/com/android/exchange/eas/EasProvision.java
+++ b/src/com/android/exchange/eas/EasProvision.java
@@ -17,12 +17,8 @@
package com.android.exchange.eas;
import android.content.Context;
-import android.content.SyncResult;
-import android.os.Bundle;
-import android.telephony.TelephonyManager;
import com.android.emailcommon.provider.Account;
-import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.PolicyServiceProxy;
import com.android.exchange.Eas;
@@ -113,20 +109,20 @@
mPhase = 0;
}
- private int performInitialRequest(final SyncResult syncResult) {
+ private int performInitialRequest() {
mPhase = PHASE_INITIAL;
- return performOperation(syncResult);
+ return performOperation();
}
- private void performAckRequestForWipe(final SyncResult syncResult) {
+ private void performAckRequestForWipe() {
mPhase = PHASE_WIPE;
- performOperation(syncResult);
+ performOperation();
}
- private int performAckRequest(final SyncResult syncResult, final boolean isPartial) {
+ private int performAckRequest(final boolean isPartial) {
mPhase = PHASE_ACKNOWLEDGE;
mStatus = isPartial ? PROVISION_STATUS_PARTIAL : PROVISION_STATUS_OK;
- return performOperation(syncResult);
+ return performOperation();
}
/**
@@ -134,10 +130,10 @@
* @return The {@link Policy} if we support it, or null otherwise.
*/
public final Policy test() {
- int result = performInitialRequest(null);
+ int result = performInitialRequest();
if (result == RESULT_POLICY_UNSUPPORTED) {
// Check if the server will permit partial policies.
- result = performAckRequest(null, true);
+ result = performAckRequest(true);
}
if (result == RESULT_POLICY_SUPPORTED) {
// The server is ok with us not supporting everything, so clear the unsupported ones.
@@ -149,19 +145,18 @@
/**
* Get the required policy from the server and enforce it.
- * @param syncResult The {@link SyncResult}, if anym for this operation.
- * @param accountId The id for the account for this request.
* @return Whether we succeeded in provisioning this account.
*/
- public final boolean provision(final SyncResult syncResult, final long accountId) {
- final int result = performInitialRequest(syncResult);
+ public final boolean provision() {
+ final int result = performInitialRequest();
+ final long accountId = getAccountId();
if (result < 0) {
return false;
}
if (result == RESULT_REMOTE_WIPE) {
- performAckRequestForWipe(syncResult);
+ performAckRequestForWipe();
LogUtils.i(LOG_TAG, "Executing remote wipe");
PolicyServiceProxy.remoteWipe(mContext);
return false;
@@ -175,8 +170,7 @@
}
// Acknowledge to the server and make sure all's well.
- if (performAckRequest(syncResult, result == RESULT_POLICY_UNSUPPORTED) ==
- RESULT_POLICY_UNSUPPORTED) {
+ if (performAckRequest(result == RESULT_POLICY_UNSUPPORTED) == RESULT_POLICY_UNSUPPORTED) {
return false;
}
@@ -190,7 +184,7 @@
if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE
|| version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
final EasSettings settingsOperation = new EasSettings(this);
- if (!settingsOperation.sendDeviceInformation(syncResult)) {
+ if (!settingsOperation.sendDeviceInformation()) {
// TODO: Do something more useful when the settings command fails.
// The consequence here is that the server will not have device info.
// However, this is NOT a provisioning failure.
@@ -266,8 +260,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
- throws IOException {
+ protected int handleResponse(final EasResponse response) throws IOException {
final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream());
// If this is the response for a remote wipe ack, it doesn't have anything useful in it.
// Just go ahead and return now.
@@ -302,7 +295,7 @@
}
@Override
- protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
+ protected boolean handleProvisionError() {
// If we get a provisioning error while doing provisioning, we should not recurse.
return false;
}
diff --git a/src/com/android/exchange/eas/EasSettings.java b/src/com/android/exchange/eas/EasSettings.java
index a04fa14..41df356 100644
--- a/src/com/android/exchange/eas/EasSettings.java
+++ b/src/com/android/exchange/eas/EasSettings.java
@@ -16,8 +16,6 @@
package com.android.exchange.eas;
-import android.content.SyncResult;
-
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.SettingsParser;
@@ -49,8 +47,8 @@
super(parentOperation);
}
- public boolean sendDeviceInformation(final SyncResult syncResult) {
- return performOperation(syncResult) == RESULT_OK;
+ public boolean sendDeviceInformation() {
+ return performOperation() == RESULT_OK;
}
@Override
@@ -68,8 +66,7 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
- throws IOException {
+ protected int handleResponse(final EasResponse response) throws IOException {
return new SettingsParser(response.getInputStream()).parse()
? RESULT_OK : RESULT_OTHER_FAILURE;
}
diff --git a/src/com/android/exchange/eas/EasSync.java b/src/com/android/exchange/eas/EasSync.java
index 084f921..39f38be 100644
--- a/src/com/android/exchange/eas/EasSync.java
+++ b/src/com/android/exchange/eas/EasSync.java
@@ -19,7 +19,6 @@
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.SyncResult;
import android.database.Cursor;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
@@ -55,6 +54,10 @@
*/
public class EasSync extends EasOperation {
+ /** Result code indicating that the mailbox for an upsync is no longer present. */
+ public final static int RESULT_NO_MAILBOX = 0;
+ public final static int RESULT_OK = 1;
+
// TODO: When we handle downsync, this will become relevant.
private boolean mInitialSync;
@@ -100,10 +103,10 @@
}
/**
- * TODO: return value doesn't do what it claims.
- * @return Number of messages successfully synced, or -1 if we encountered an error.
+ * @return Number of messages successfully synced, or a negative response code from
+ * {@link EasOperation} if we encountered any errors.
*/
- public final int upsync(final SyncResult syncResult) {
+ public final int upsync() {
final List<MessageStateChange> changes = MessageStateChange.getChanges(mContext,
getAccountId(), getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE);
if (changes == null) {
@@ -117,43 +120,65 @@
final long[][] messageIds = new long[2][changes.size()];
final int[] counts = new int[2];
+ int result = 0;
for (int i = 0; i < allData.size(); ++i) {
mMailboxId = allData.keyAt(i);
mStateChanges = allData.valueAt(i);
- final Cursor mailboxCursor = mContext.getContentResolver().query(
- ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId),
- Mailbox.ProjectionSyncData.PROJECTION, null, null, null);
- if (mailboxCursor != null) {
- try {
- if (mailboxCursor.moveToFirst()) {
- mMailboxServerId = mailboxCursor.getString(
- Mailbox.ProjectionSyncData.COLUMN_SERVER_ID);
- mMailboxSyncKey = mailboxCursor.getString(
- Mailbox.ProjectionSyncData.COLUMN_SYNC_KEY);
- final int result;
- if (TextUtils.isEmpty(mMailboxSyncKey) || mMailboxSyncKey.equals("0")) {
- // For some reason we can get here without a valid mailbox sync key
- // b/10797675
- // TODO: figure out why and clean this up
- LogUtils.d(LOG_TAG,
- "Tried to sync mailbox %d with invalid mailbox sync key",
- mMailboxId);
- result = -1;
- } else {
- result = performOperation(syncResult);
- }
- if (result == 0) {
- handleMessageUpdateStatus(mMessageUpdateStatus, messageIds, counts);
- } else {
- for (final MessageStateChange msc : mStateChanges) {
- messageIds[1][counts[1]] = msc.getMessageId();
- ++counts[1];
+ boolean retryMailbox = true;
+ // If we've already encountered a fatal error, don't even try to upsync subsequent
+ // mailboxes.
+ if (result >= 0) {
+ final Cursor mailboxCursor = mContext.getContentResolver().query(
+ ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailboxId),
+ Mailbox.ProjectionSyncData.PROJECTION, null, null, null);
+ if (mailboxCursor != null) {
+ try {
+ if (mailboxCursor.moveToFirst()) {
+ mMailboxServerId = mailboxCursor.getString(
+ Mailbox.ProjectionSyncData.COLUMN_SERVER_ID);
+ mMailboxSyncKey = mailboxCursor.getString(
+ Mailbox.ProjectionSyncData.COLUMN_SYNC_KEY);
+ if (TextUtils.isEmpty(mMailboxSyncKey) || mMailboxSyncKey.equals("0")) {
+ // For some reason we can get here without a valid mailbox sync key
+ // b/10797675
+ // TODO: figure out why and clean this up
+ LogUtils.d(LOG_TAG,
+ "Tried to sync mailbox %d with invalid mailbox sync key",
+ mMailboxId);
+ } else {
+ result = performOperation();
+ if (result >= 0) {
+ // Our request gave us back a legitimate answer; this is the
+ // only case in which we don't retry this mailbox.
+ retryMailbox = false;
+ if (result == RESULT_OK) {
+ handleMessageUpdateStatus(mMessageUpdateStatus, messageIds,
+ counts);
+ } else if (result == RESULT_NO_MAILBOX) {
+ // A retry here is pointless -- the message's mailbox (and
+ // therefore the message) is gone, so mark as success so
+ // that these entries get wiped from the change list.
+ for (final MessageStateChange msc : mStateChanges) {
+ messageIds[0][counts[0]] = msc.getMessageId();
+ ++counts[0];
+ }
+ } else {
+ LogUtils.wtf(LOG_TAG, "Unrecognized result code: %d",
+ result);
+ }
+ }
}
}
+ } finally {
+ mailboxCursor.close();
}
- } finally {
- mailboxCursor.close();
+ }
+ }
+ if (retryMailbox) {
+ for (final MessageStateChange msc : mStateChanges) {
+ messageIds[1][counts[1]] = msc.getMessageId();
+ ++counts[1];
}
}
}
@@ -162,7 +187,10 @@
MessageStateChange.upsyncSuccessful(cr, messageIds[0], counts[0]);
MessageStateChange.upsyncRetry(cr, messageIds[1], counts[1]);
- return 0;
+ if (result < 0) {
+ return result;
+ }
+ return counts[0];
}
@Override
@@ -182,11 +210,11 @@
}
@Override
- protected int handleResponse(final EasResponse response, final SyncResult syncResult)
+ protected int handleResponse(final EasResponse response)
throws IOException, CommandStatusException {
final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, mMailboxId);
if (mailbox == null) {
- return RESULT_OTHER_FAILURE;
+ return RESULT_NO_MAILBOX;
}
final EmailSyncParser parser = new EmailSyncParser(mContext, mContext.getContentResolver(),
response.getInputStream(), mailbox, mAccount);
@@ -196,7 +224,7 @@
} catch (final Parser.EmptyStreamException e) {
// This indicates a compressed response which was empty, which is OK.
}
- return 0;
+ return RESULT_OK;
}
@Override
diff --git a/src/com/android/exchange/eas/EasSyncBase.java b/src/com/android/exchange/eas/EasSyncBase.java
new file mode 100644
index 0000000..90237dd
--- /dev/null
+++ b/src/com/android/exchange/eas/EasSyncBase.java
@@ -0,0 +1,138 @@
+package com.android.exchange.eas;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.Mailbox;
+import com.android.exchange.CommandStatusException;
+import com.android.exchange.Eas;
+import com.android.exchange.EasResponse;
+import com.android.exchange.adapter.AbstractSyncParser;
+import com.android.exchange.adapter.Parser;
+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;
+
+/**
+ * Performs an EAS downsync operation for one folder.
+ * TODO: Merge with {@link EasSync}, which currently handles upsync.
+ */
+public class EasSyncBase extends EasOperation {
+
+ private static final String TAG = Eas.LOG_TAG;
+
+ public static final int RESULT_DONE = 0;
+ public static final int RESULT_MORE_AVAILABLE = 1;
+
+ private final boolean mInitialSync;
+ private final Mailbox mMailbox;
+
+ private int mNumWindows;
+
+ // TODO: Convert to accountId when ready to convert to EasService.
+ public EasSyncBase(final Context context, final Account account, final Mailbox mailbox) {
+ super(context, account);
+ // TODO: This works for email, but not necessarily for other types.
+ mInitialSync = EmailContent.isInitialSyncKey(getSyncKey());
+ mMailbox = mailbox;
+ }
+
+ /**
+ * Get the sync key for this mailbox.
+ * @return The sync key for the object being synced. "0" means this is the first sync. If
+ * there is an error in getting the sync key, this function returns null.
+ */
+ protected String getSyncKey() {
+ if (mMailbox == null) {
+ return null;
+ }
+ if (mMailbox.mSyncKey == null) {
+ mMailbox.mSyncKey = "0";
+ }
+ return mMailbox.mSyncKey;
+ }
+
+ @Override
+ protected String getCommand() {
+ return "Sync";
+ }
+
+ @Override
+ protected HttpEntity getRequestEntity() throws IOException {
+ final String className = Eas.getFolderClass(mMailbox.mType);
+ final String syncKey = getSyncKey();
+ LogUtils.d(TAG, "Syncing account %d mailbox %d (class %s) with syncKey %s", mAccount.mId,
+ mMailbox.mId, className, syncKey);
+
+ final Serializer s = new Serializer();
+ s.start(Tags.SYNC_SYNC);
+ s.start(Tags.SYNC_COLLECTIONS);
+ s.start(Tags.SYNC_COLLECTION);
+ // The "Class" element is removed in EAS 12.1 and later versions
+ if (getProtocolVersion() < Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE) {
+ s.data(Tags.SYNC_CLASS, className);
+ }
+ s.data(Tags.SYNC_SYNC_KEY, syncKey);
+ s.data(Tags.SYNC_COLLECTION_ID, mMailbox.mServerId);
+ if (mInitialSync) {
+ //setInitialSyncOptions(s);
+ } else {
+ //setNonInitialSyncOptions(s, mNumWindows);
+ //setUpsyncCommands(s);
+ }
+ s.end().end().end().done();
+
+ return makeEntity(s);
+ }
+
+ @Override
+ protected int handleResponse(final EasResponse response)
+ throws IOException, CommandStatusException {
+ try {
+ final AbstractSyncParser parser = null;//getParser(response.getInputStream());
+ final boolean moreAvailable = parser.parse();
+ if (moreAvailable) {
+ return RESULT_MORE_AVAILABLE;
+ }
+ } catch (final Parser.EmptyStreamException e) {
+ // This indicates a compressed response which was empty, which is OK.
+ }
+ return RESULT_DONE;
+ }
+
+ @Override
+ public int performOperation() {
+ int result = RESULT_MORE_AVAILABLE;
+ mNumWindows = 1;
+ String key = getSyncKey();
+ while (result == RESULT_MORE_AVAILABLE) {
+ result = super.performOperation();
+ // TODO: Clear pending request queue.
+ final String newKey = getSyncKey();
+ if (result == RESULT_MORE_AVAILABLE && key.equals(newKey)) {
+ LogUtils.e(TAG,
+ "Server has more data but we have the same key: %s numWindows: %d",
+ key, mNumWindows);
+ mNumWindows++;
+ } else {
+ mNumWindows = 1;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected long getTimeout() {
+ if (mInitialSync) {
+ return 120 * DateUtils.SECOND_IN_MILLIS;
+ }
+ return super.getTimeout();
+ }
+
+}
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index b5081f6..45c93f2 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -19,7 +19,6 @@
import android.app.Service;
import android.content.ContentResolver;
import android.content.Intent;
-import android.content.SyncResult;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -79,16 +78,20 @@
LogUtils.d(TAG, "IEmailService.loadAttachment: %d", attachmentId);
final EasLoadAttachment operation = new EasLoadAttachment(EasService.this, accountId,
attachmentId, callback);
- doOperation(operation, null, "IEmailService.loadAttachment");
+ doOperation(operation, "IEmailService.loadAttachment");
}
@Override
public void updateFolderList(final long accountId) {
final EasFolderSync operation = new EasFolderSync(EasService.this, accountId);
- doOperation(operation, null, "IEmailService.updateFolderList");
+ doOperation(operation, "IEmailService.updateFolderList");
}
@Override
+ public void sync(final long accountId, final boolean updateFolderList,
+ final int mailboxType, final long[] folders) {}
+
+ @Override
public void pushModify(final long accountId) {
LogUtils.d(TAG, "IEmailService.pushModify: %d", accountId);
final Account account = Account.restoreAccountWithId(EasService.this, accountId);
@@ -102,7 +105,7 @@
@Override
public Bundle validate(final HostAuth hostAuth) {
final EasFolderSync operation = new EasFolderSync(EasService.this, hostAuth);
- doOperation(operation, null, "IEmailService.validate");
+ doOperation(operation, "IEmailService.validate");
return operation.getValidationResult();
}
@@ -227,8 +230,7 @@
return START_STICKY;
}
- public int doOperation(final EasOperation operation, final SyncResult syncResult,
- final String loggingName) {
+ public int doOperation(final EasOperation operation, final String loggingName) {
final long accountId = operation.getAccountId();
LogUtils.d(TAG, "%s: %d", loggingName, accountId);
mSynchronizer.syncStart(accountId);
@@ -238,7 +240,7 @@
// it in the finally block below.
// On the other hand, even for SAs, it doesn't hurt to get a wakelock here.
try {
- return operation.performOperation(syncResult);
+ return operation.performOperation();
} finally {
// TODO: Fix pushEnabled param
mSynchronizer.syncEnd(accountId, false);
diff --git a/src/com/android/exchange/service/EasSyncHandler.java b/src/com/android/exchange/service/EasSyncHandler.java
index 0b6ee1c..6614399 100644
--- a/src/com/android/exchange/service/EasSyncHandler.java
+++ b/src/com/android/exchange/service/EasSyncHandler.java
@@ -375,7 +375,7 @@
} else if (resp.isProvisionError()
|| responseResult == SYNC_RESULT_PROVISIONING_ERROR) {
final EasProvision provision = new EasProvision(mContext, mAccount, this);
- if (provision.provision(syncResult, mAccount.mId)) {
+ if (provision.provision()) {
// We handled the provisioning error, so loop.
LogUtils.d(TAG, "Provisioning error handled during sync, retrying");
result = SYNC_RESULT_MORE_AVAILABLE;
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 65d9b00..eb17500 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -430,7 +430,7 @@
// TODO: Prevent this from happening in parallel with a sync?
final EasLoadAttachment operation = new EasLoadAttachment(EmailSyncAdapterService.this,
accountId, attachmentId, callback);
- operation.performOperation(null);
+ operation.performOperation();
}
@Override
@@ -469,7 +469,26 @@
public void sendMail(final long accountId) {}
@Override
- public void pushModify(final long accountId) {}
+ public void pushModify(final long accountId) {
+ LogUtils.d(TAG, "IEmailService.pushModify");
+ if (mEasService != null) {
+ try {
+ mEasService.pushModify(accountId);
+ return;
+ } catch (final RemoteException re) {
+ LogUtils.e(TAG, re, "While asking EasService to handle pushModify");
+ }
+ }
+ final Account account = Account.restoreAccountWithId(EmailSyncAdapterService.this,
+ accountId);
+ if (account != null) {
+ mSyncHandlerMap.modifyPing(false, account);
+ }
+ }
+
+ @Override
+ public void sync(final long accountId, final boolean updateFolderList,
+ final int mailboxType, final long[] folders) {}
};
public EmailSyncAdapterService() {
@@ -674,39 +693,60 @@
// If we're just twiddling the push, we do the lightweight thing and bail early.
if (pushOnly && !isFolderSync) {
- mSyncHandlerMap.modifyPing(false, account);
LogUtils.d(TAG, "onPerformSync: mailbox push only");
+ if (mEasService != null) {
+ try {
+ mEasService.pushModify(account.mId);
+ return;
+ } catch (final RemoteException re) {
+ LogUtils.e(TAG, re, "While trying to pushModify within onPerformSync");
+ }
+ }
+ mSyncHandlerMap.modifyPing(false, account);
return;
}
// Do the bookkeeping for starting a sync, including stopping a ping if necessary.
mSyncHandlerMap.startSync(account.mId);
-
- // Perform a FolderSync if necessary.
- // TODO: We permit FolderSync even during security hold, because it's necessary to
- // resolve some holds. Ideally we would only do it for the holds that require it.
- if (isFolderSync) {
- final EasFolderSync folderSync = new EasFolderSync(context, account);
- folderSync.doFolderSync(syncResult);
- }
-
boolean lastSyncHadError = false;
- if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) == 0) {
+ try {
+ // Perform a FolderSync if necessary.
+ // TODO: We permit FolderSync even during security hold, because it's necessary to
+ // resolve some holds. Ideally we would only do it for the holds that require it.
+ if (isFolderSync) {
+ final EasFolderSync folderSync = new EasFolderSync(context, account);
+ final int result = folderSync.doFolderSync();
+ if (result < 0) {
+ EasFolderSync.writeResultToSyncResult(result, syncResult);
+ return;
+ }
+ }
+
+ // Do not permit further syncs if we're on security hold.
+ if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
+ return;
+ }
+
// Perform email upsync for this account. Moves first, then state changes.
if (!isInitialSync) {
EasMoveItems move = new EasMoveItems(context, account);
- move.upsyncMovedMessages(syncResult);
+ final int moveResult = move.upsyncMovedMessages();
+ if (moveResult < 0) {
+ EasMoveItems.writeResultToSyncResult(moveResult, syncResult);
+ return;
+ }
+
// TODO: EasSync should eventually handle both up and down; for now, it's used
// purely for upsync.
EasSync upsync = new EasSync(context, account);
- upsync.upsync(syncResult);
+ final int upsyncResult = upsync.upsync();
+ if (upsyncResult < 0) {
+ EasSync.writeResultToSyncResult(upsyncResult, syncResult);
+ return;
+ }
}
- // TODO: Should we refresh 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.
-
if (mailboxIds != null) {
long numIoExceptions = 0;
long numAuthExceptions = 0;
@@ -761,15 +801,15 @@
}
}
}
+ } finally {
+ // Clean up the bookkeeping, including restarting ping if necessary.
+ mSyncHandlerMap.syncComplete(lastSyncHadError, account);
+
+ // TODO: It may make sense to have common error handling here. Two possibilities:
+ // 1) performSync return value can signal some useful info.
+ // 2) syncResult can contain useful info.
+ LogUtils.d(TAG, "onPerformSync: finished");
}
-
- // Clean up the bookkeeping, including restarting ping if necessary.
- mSyncHandlerMap.syncComplete(lastSyncHadError, account);
-
- // TODO: It may make sense to have common error handling here. Two possible mechanisms:
- // 1) performSync return value can signal some useful info.
- // 2) syncResult can contain useful info.
- LogUtils.d(TAG, "onPerformSync: finished");
}
/**