| /* |
| * 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.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.exchange.service.EasService; |
| 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; |
| |
| /** 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; |
| |
| /** During validation, this holds the result. */ |
| private Bundle mValidationResult; |
| |
| /** |
| * Constructor for use with {@link EasService} when performing an actual sync. |
| * @param context |
| * @param accountId |
| */ |
| public EasFolderSync(final Context context, final long accountId) { |
| super(context, accountId); |
| mStatusOnly = false; |
| mPolicy = null; |
| } |
| |
| /** |
| * Constructor for actually doing folder sync. |
| * @param context |
| * @param account |
| */ |
| public EasFolderSync(final Context context, final Account account) { |
| super(context, account); |
| mStatusOnly = false; |
| mPolicy = null; |
| } |
| |
| /** |
| * Constructor for account validation. |
| * @param context |
| * @param hostAuth |
| */ |
| public EasFolderSync(final Context context, final HostAuth hostAuth) { |
| super(context, -1); |
| setDummyAccount(hostAuth); |
| mStatusOnly = true; |
| } |
| |
| @Override |
| public int performOperation() { |
| if (mStatusOnly) { |
| return validate(); |
| } else { |
| LogUtils.d(LOG_TAG, "Performing FolderSync for account %d", getAccountId()); |
| return super.performOperation(); |
| } |
| } |
| |
| /** |
| * Returns the validation results after this operation has been performed. |
| * @return The validation results. |
| */ |
| public Bundle getValidationResult() { |
| return mValidationResult; |
| } |
| |
| /** |
| * Perform a folder sync. |
| * TODO: Remove this function when transition to EasService is complete. |
| * @return A result code, either from above or from the base class. |
| */ |
| public int doFolderSync() { |
| if (mStatusOnly) { |
| return RESULT_WRONG_OPERATION; |
| } |
| LogUtils.d(LOG_TAG, "Performing sync for account %d", getAccountId()); |
| // 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(); |
| } |
| |
| /** |
| * Helper function for {@link #performOperation} -- do some initial checks and, if they pass, |
| * perform a folder sync to verify that we can. This sets {@link #mValidationResult} as a side |
| * effect which holds the result details needed by the UI. |
| * @return A result code, either from above or from the base class. |
| */ |
| private int validate() { |
| mValidationResult = new Bundle(3); |
| if (!mStatusOnly) { |
| writeResultCode(mValidationResult, RESULT_OTHER_FAILURE); |
| return RESULT_OTHER_FAILURE; |
| } |
| LogUtils.d(LOG_TAG, "Performing validation"); |
| |
| if (!registerClientCert()) { |
| mValidationResult.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, |
| MessagingException.CLIENT_CERTIFICATE_ERROR); |
| return RESULT_CLIENT_CERTIFICATE_REQUIRED; |
| } |
| |
| if (shouldGetProtocolVersion()) { |
| final EasOptions options = new EasOptions(this); |
| final int result = options.getProtocolVersionFromServer(); |
| if (result != EasOptions.RESULT_OK) { |
| writeResultCode(mValidationResult, result); |
| return result; |
| } |
| final String protocolVersion = options.getProtocolVersionString(); |
| setProtocolVersion(protocolVersion); |
| mValidationResult.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, |
| protocolVersion); |
| } |
| |
| // 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(); |
| writeResultCode(mValidationResult, result); |
| return result; |
| } |
| |
| /** |
| * Perform account validation. |
| * TODO: Remove this function when transition to EasService is complete. |
| * @return The response {@link Bundle} expected by the RPC. |
| */ |
| public Bundle doValidate() { |
| validate(); |
| return mValidationResult; |
| } |
| |
| @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) |
| throws IOException, CommandStatusException { |
| if (!response.isEmpty()) { |
| new FolderSyncParser(mContext, mContext.getContentResolver(), |
| response.getInputStream(), mAccount, mStatusOnly).parse(); |
| } |
| return RESULT_OK; |
| } |
| |
| @Override |
| protected boolean handleForbidden() { |
| return mStatusOnly; |
| } |
| |
| @Override |
| protected boolean handleProvisionError() { |
| 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(); |
| } |
| |
| /** |
| * 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); |
| } |
| } |