blob: 63ea753f24f706c316edf8e376833d4a6ee381f8 [file] [log] [blame]
/*
* Copyright (C) 2013 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.Bundle;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.exchange.CommandStatusException;
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.FolderSyncParser;
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;
/**
* Implements the EAS FolderSync command. We use this both to actually do a folder sync, and also
* during account adding flow as a convenient command to validate the account settings (e.g. since
* it needs to login and will tell us about provisioning requirements).
* TODO: Doing validation here is kind of wonky. There must be a better way.
* TODO: Add the use of the Settings command during validation.
*
* See http://msdn.microsoft.com/en-us/library/ee237648(v=exchg.80).aspx for more details.
*/
public class EasFolderSync extends EasOperation {
/** Result code indicating the sync completed correctly. */
public static final int RESULT_OK = 1;
/**
* Result code indicating that this object was constructed for sync and was asked to validate,
* or vice versa.
*/
public static final int RESULT_WRONG_OPERATION = 2;
// TODO: Eliminate the need for mAccount (requires FolderSyncParser changes).
private final Account mAccount;
/** Indicates whether this object is for validation rather than sync. */
private final boolean mStatusOnly;
/** During validation, this holds the policy we must enforce. */
private Policy mPolicy;
/**
* Constructor for actually doing folder sync.
* @param context
* @param account
*/
public EasFolderSync(final Context context, final Account account) {
super(context, account);
mAccount = account;
mStatusOnly = false;
mPolicy = null;
}
/**
* Constructor for account validation.
* @param context
* @param hostAuth
*/
public EasFolderSync(final Context context, final HostAuth hostAuth) {
this(context, new Account(), hostAuth);
}
private EasFolderSync(final Context context, final Account account, final HostAuth hostAuth) {
super(context, account, hostAuth);
mAccount = account;
mAccount.mEmailAddress = hostAuth.mLogin;
mStatusOnly = true;
}
/**
* Perform a folder sync.
* @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) {
if (mStatusOnly) {
return RESULT_WRONG_OPERATION;
}
LogUtils.i(LOG_TAG, "Performing sync for account %d", mAccount.mId);
return performOperation(syncResult);
}
/**
* Perform account validation.
* @return The response {@link Bundle} expected by the RPC.
*/
public Bundle validate() {
final Bundle bundle = new Bundle(3);
if (!mStatusOnly) {
writeResultCode(bundle, RESULT_OTHER_FAILURE);
return bundle;
}
LogUtils.i(LOG_TAG, "Performing validation");
if (!registerClientCert()) {
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
MessagingException.CLIENT_CERTIFICATE_ERROR);
return bundle;
}
if (shouldGetProtocolVersion()) {
final EasOptions options = new EasOptions(this);
final int result = options.getProtocolVersionFromServer(null);
if (result != EasOptions.RESULT_OK) {
writeResultCode(bundle, result);
return bundle;
}
final String protocolVersion = options.getProtocolVersionString();
setProtocolVersion(protocolVersion);
bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, protocolVersion);
}
writeResultCode(bundle, performOperation(null));
return bundle;
}
@Override
protected String getCommand() {
return "FolderSync";
}
@Override
protected HttpEntity getRequestEntity() throws IOException {
final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
final Serializer s = new Serializer();
s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
.end().end().done();
return makeEntity(s);
}
@Override
protected int handleResponse(final EasResponse response, final SyncResult syncResult)
throws IOException {
if (!response.isEmpty()) {
try {
new FolderSyncParser(mContext, mContext.getContentResolver(),
response.getInputStream(), mAccount, mStatusOnly).parse();
} catch (final CommandStatusException e) {
final int status = e.mStatus;
LogUtils.d(LOG_TAG, "EasFolderSync.handleResponse status %d", status);
if (CommandStatusException.CommandStatus.isNeedsProvisioning(status)) {
return RESULT_PROVISIONING_ERROR;
}
if (CommandStatusException.CommandStatus.isDeniedAccess(status)) {
return RESULT_FORBIDDEN;
}
return RESULT_OTHER_FAILURE;
}
}
return RESULT_OK;
}
@Override
protected boolean handleForbidden() {
return mStatusOnly;
}
@Override
protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
if (mStatusOnly) {
final EasProvision provisionOperation = new EasProvision(this);
mPolicy = provisionOperation.test();
// Regardless of whether the policy is supported, we return false because there's
// no need to re-run the operation.
return false;
}
return super.handleProvisionError(syncResult, accountId);
}
/**
* Translate {@link EasOperation} result codes to the values needed by the RPC, and write
* them to the {@link Bundle}.
* @param bundle The {@link Bundle} to return to the RPC.
* @param resultCode The result code for this operation.
*/
private void writeResultCode(final Bundle bundle, final int resultCode) {
final int messagingExceptionCode;
switch (resultCode) {
case RESULT_ABORT:
messagingExceptionCode = MessagingException.IOERROR;
break;
case RESULT_RESTART:
messagingExceptionCode = MessagingException.IOERROR;
break;
case RESULT_TOO_MANY_REDIRECTS:
messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
break;
case RESULT_REQUEST_FAILURE:
messagingExceptionCode = MessagingException.IOERROR;
break;
case RESULT_FORBIDDEN:
messagingExceptionCode = MessagingException.ACCESS_DENIED;
break;
case RESULT_PROVISIONING_ERROR:
if (mPolicy == null) {
messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
} else {
bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
MessagingException.SECURITY_POLICIES_REQUIRED :
MessagingException.SECURITY_POLICIES_UNSUPPORTED;
}
break;
case RESULT_AUTHENTICATION_ERROR:
messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
break;
case RESULT_CLIENT_CERTIFICATE_REQUIRED:
messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
break;
case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
break;
case RESULT_OTHER_FAILURE:
messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
break;
case RESULT_OK:
messagingExceptionCode = MessagingException.NO_ERROR;
break;
default:
messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
break;
}
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
}
}