/* * 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; } /** * 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; } @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_NETWORK_PROBLEM: 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); } }