Home | History | Annotate | Download | only in eas
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.exchange.eas;
     18 
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 
     22 import com.android.emailcommon.mail.MessagingException;
     23 import com.android.emailcommon.provider.Account;
     24 import com.android.emailcommon.provider.HostAuth;
     25 import com.android.emailcommon.provider.Policy;
     26 import com.android.emailcommon.service.EmailServiceProxy;
     27 import com.android.exchange.CommandStatusException;
     28 import com.android.exchange.EasResponse;
     29 import com.android.exchange.adapter.FolderSyncParser;
     30 import com.android.exchange.adapter.Serializer;
     31 import com.android.exchange.adapter.Tags;
     32 import com.android.mail.utils.LogUtils;
     33 
     34 import org.apache.http.HttpEntity;
     35 
     36 import java.io.IOException;
     37 
     38 /**
     39  * Implements the EAS FolderSync command. We use this both to actually do a folder sync, and also
     40  * during account adding flow as a convenient command to validate the account settings (e.g. since
     41  * it needs to login and will tell us about provisioning requirements).
     42  * TODO: Doing validation here is kind of wonky. There must be a better way.
     43  * TODO: Add the use of the Settings command during validation.
     44  *
     45  * See http://msdn.microsoft.com/en-us/library/ee237648(v=exchg.80).aspx for more details.
     46  */
     47 public class EasFolderSync extends EasOperation {
     48 
     49     /** Result code indicating the sync completed correctly. */
     50     public static final int RESULT_OK = 1;
     51     /**
     52      * Result code indicating that this object was constructed for sync and was asked to validate,
     53      * or vice versa.
     54      */
     55     public static final int RESULT_WRONG_OPERATION = 2;
     56 
     57     /** Indicates whether this object is for validation rather than sync. */
     58     private final boolean mStatusOnly;
     59 
     60     /** During validation, this holds the policy we must enforce. */
     61     private Policy mPolicy;
     62 
     63     /** During validation, this holds the result. */
     64     private Bundle mValidationResult;
     65 
     66     /**
     67      * Constructor for actually doing folder sync.
     68      * @param context
     69      * @param account
     70      */
     71     public EasFolderSync(final Context context, final Account account) {
     72         super(context, account);
     73         mStatusOnly = false;
     74         mPolicy = null;
     75     }
     76 
     77     private static Account makeAccount(final HostAuth hostAuth) {
     78         final Account account = new Account();
     79         account.mHostAuthRecv = hostAuth;
     80         account.mEmailAddress = hostAuth.mLogin;
     81         return account;
     82     }
     83 
     84     /**
     85      * Constructor for account validation.
     86      * @param context
     87      * @param hostAuth
     88      */
     89     public EasFolderSync(final Context context, final HostAuth hostAuth) {
     90         super(context, makeAccount(hostAuth), hostAuth);
     91         mStatusOnly = true;
     92     }
     93 
     94     @Override
     95     public int performOperation() {
     96         if (mStatusOnly) {
     97             return validate();
     98         } else {
     99             LogUtils.d(LOG_TAG, "Performing FolderSync for account %d", getAccountId());
    100             return super.performOperation();
    101         }
    102     }
    103 
    104     /**
    105      * Returns the validation results after this operation has been performed.
    106      * @return The validation results.
    107      */
    108     public Bundle getValidationResult() {
    109         return mValidationResult;
    110     }
    111 
    112     /**
    113      * Helper function for {@link #performOperation} -- do some initial checks and, if they pass,
    114      * perform a folder sync to verify that we can. This sets {@link #mValidationResult} as a side
    115      * effect which holds the result details needed by the UI.
    116      * @return A result code, either from above or from the base class.
    117      */
    118     private int validate() {
    119         mValidationResult = new Bundle(3);
    120         if (!mStatusOnly) {
    121             writeResultCode(mValidationResult, RESULT_OTHER_FAILURE);
    122             return RESULT_OTHER_FAILURE;
    123         }
    124         LogUtils.d(LOG_TAG, "Performing validation");
    125 
    126         if (!registerClientCert()) {
    127             mValidationResult.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
    128                     MessagingException.CLIENT_CERTIFICATE_ERROR);
    129             return RESULT_CLIENT_CERTIFICATE_REQUIRED;
    130         }
    131 
    132         if (shouldGetProtocolVersion()) {
    133             final EasOptions options = new EasOptions(this);
    134             final int result = options.getProtocolVersionFromServer();
    135             if (result != EasOptions.RESULT_OK) {
    136                 writeResultCode(mValidationResult, result);
    137                 return result;
    138             }
    139             final String protocolVersion = options.getProtocolVersionString();
    140             setProtocolVersion(protocolVersion);
    141             mValidationResult.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION,
    142                     protocolVersion);
    143         }
    144 
    145         // This is intentionally a call to super.performOperation. This is a helper function for
    146         // our version of perfomOperation so calling that function would infinite loop.
    147         final int result = super.performOperation();
    148         writeResultCode(mValidationResult, result);
    149         return result;
    150     }
    151 
    152     @Override
    153     protected String getCommand() {
    154         return "FolderSync";
    155     }
    156 
    157     @Override
    158     protected HttpEntity getRequestEntity() throws IOException {
    159         final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
    160         final Serializer s = new Serializer();
    161         s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
    162             .end().end().done();
    163         return makeEntity(s);
    164     }
    165 
    166     @Override
    167     protected int handleResponse(final EasResponse response)
    168             throws IOException, CommandStatusException {
    169         if (!response.isEmpty()) {
    170             new FolderSyncParser(mContext, mContext.getContentResolver(),
    171                     response.getInputStream(), mAccount, mStatusOnly).parse();
    172         }
    173         return RESULT_OK;
    174     }
    175 
    176     @Override
    177     protected boolean handleForbidden() {
    178         return mStatusOnly;
    179     }
    180 
    181     @Override
    182     protected boolean handleProvisionError() {
    183         if (mStatusOnly) {
    184             final EasProvision provisionOperation = new EasProvision(this);
    185             mPolicy = provisionOperation.test();
    186             // Regardless of whether the policy is supported, we return false because there's
    187             // no need to re-run the operation.
    188             return false;
    189         }
    190         return super.handleProvisionError();
    191     }
    192 
    193     /**
    194      * Translate {@link EasOperation} result codes to the values needed by the RPC, and write
    195      * them to the {@link Bundle}.
    196      * @param bundle The {@link Bundle} to return to the RPC.
    197      * @param resultCode The result code for this operation.
    198      */
    199     private void writeResultCode(final Bundle bundle, final int resultCode) {
    200         final int messagingExceptionCode;
    201         switch (resultCode) {
    202             case RESULT_ABORT:
    203                 messagingExceptionCode = MessagingException.IOERROR;
    204                 break;
    205             case RESULT_RESTART:
    206                 messagingExceptionCode = MessagingException.IOERROR;
    207                 break;
    208             case RESULT_TOO_MANY_REDIRECTS:
    209                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    210                 break;
    211             case RESULT_NETWORK_PROBLEM:
    212                 messagingExceptionCode = MessagingException.IOERROR;
    213                 break;
    214             case RESULT_FORBIDDEN:
    215                 messagingExceptionCode = MessagingException.ACCESS_DENIED;
    216                 break;
    217             case RESULT_PROVISIONING_ERROR:
    218                 if (mPolicy == null) {
    219                     messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    220                 } else {
    221                     bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
    222                     messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
    223                             MessagingException.SECURITY_POLICIES_REQUIRED :
    224                             MessagingException.SECURITY_POLICIES_UNSUPPORTED;
    225                 }
    226                 break;
    227             case RESULT_AUTHENTICATION_ERROR:
    228                 messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
    229                 break;
    230             case RESULT_CLIENT_CERTIFICATE_REQUIRED:
    231                 messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
    232                 break;
    233             case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
    234                 messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
    235                 break;
    236             case RESULT_OTHER_FAILURE:
    237                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    238                 break;
    239             case RESULT_OK:
    240                 messagingExceptionCode = MessagingException.NO_ERROR;
    241                 break;
    242             default:
    243                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    244                 break;
    245         }
    246         bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
    247     }
    248 }
    249