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.exchange.service.EasService;
     33 import com.android.mail.utils.LogUtils;
     34 
     35 import org.apache.http.HttpEntity;
     36 
     37 import java.io.IOException;
     38 
     39 /**
     40  * Implements the EAS FolderSync command. We use this both to actually do a folder sync, and also
     41  * during account adding flow as a convenient command to validate the account settings (e.g. since
     42  * it needs to login and will tell us about provisioning requirements).
     43  * TODO: Doing validation here is kind of wonky. There must be a better way.
     44  * TODO: Add the use of the Settings command during validation.
     45  *
     46  * See http://msdn.microsoft.com/en-us/library/ee237648(v=exchg.80).aspx for more details.
     47  */
     48 public class EasFolderSync extends EasOperation {
     49 
     50     /** Result code indicating the sync completed correctly. */
     51     public static final int RESULT_OK = 1;
     52     /**
     53      * Result code indicating that this object was constructed for sync and was asked to validate,
     54      * or vice versa.
     55      */
     56     public static final int RESULT_WRONG_OPERATION = 2;
     57 
     58     /** Indicates whether this object is for validation rather than sync. */
     59     private final boolean mStatusOnly;
     60 
     61     /** During validation, this holds the policy we must enforce. */
     62     private Policy mPolicy;
     63 
     64     /** During validation, this holds the result. */
     65     private Bundle mValidationResult;
     66 
     67     /**
     68      * Constructor for use with {@link EasService} when performing an actual sync.
     69      * @param context
     70      * @param accountId
     71      */
     72     public EasFolderSync(final Context context, final long accountId) {
     73         super(context, accountId);
     74         mStatusOnly = false;
     75         mPolicy = null;
     76     }
     77 
     78     /**
     79      * Constructor for actually doing folder sync.
     80      * @param context
     81      * @param account
     82      */
     83     public EasFolderSync(final Context context, final Account account) {
     84         super(context, account);
     85         mStatusOnly = false;
     86         mPolicy = null;
     87     }
     88 
     89     /**
     90      * Constructor for account validation.
     91      * @param context
     92      * @param hostAuth
     93      */
     94     public EasFolderSync(final Context context, final HostAuth hostAuth) {
     95         super(context, -1);
     96         setDummyAccount(hostAuth);
     97         mStatusOnly = true;
     98     }
     99 
    100     @Override
    101     public int performOperation() {
    102         if (mStatusOnly) {
    103             return validate();
    104         } else {
    105             LogUtils.d(LOG_TAG, "Performing FolderSync for account %d", getAccountId());
    106             return super.performOperation();
    107         }
    108     }
    109 
    110     /**
    111      * Returns the validation results after this operation has been performed.
    112      * @return The validation results.
    113      */
    114     public Bundle getValidationResult() {
    115         return mValidationResult;
    116     }
    117 
    118     /**
    119      * Perform a folder sync.
    120      * TODO: Remove this function when transition to EasService is complete.
    121      * @return A result code, either from above or from the base class.
    122      */
    123     public int doFolderSync() {
    124         if (mStatusOnly) {
    125             return RESULT_WRONG_OPERATION;
    126         }
    127         LogUtils.d(LOG_TAG, "Performing sync for account %d", getAccountId());
    128         // This intentionally calls super.performOperation -- calling our performOperation
    129         // will simply end up calling super.performOperation anyway. This is part of the transition
    130         // to EasService and will go away when this function is deleted.
    131         return super.performOperation();
    132     }
    133 
    134     /**
    135      * Helper function for {@link #performOperation} -- do some initial checks and, if they pass,
    136      * perform a folder sync to verify that we can. This sets {@link #mValidationResult} as a side
    137      * effect which holds the result details needed by the UI.
    138      * @return A result code, either from above or from the base class.
    139      */
    140     private int validate() {
    141         mValidationResult = new Bundle(3);
    142         if (!mStatusOnly) {
    143             writeResultCode(mValidationResult, RESULT_OTHER_FAILURE);
    144             return RESULT_OTHER_FAILURE;
    145         }
    146         LogUtils.d(LOG_TAG, "Performing validation");
    147 
    148         if (!registerClientCert()) {
    149             mValidationResult.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
    150                     MessagingException.CLIENT_CERTIFICATE_ERROR);
    151             return RESULT_CLIENT_CERTIFICATE_REQUIRED;
    152         }
    153 
    154         if (shouldGetProtocolVersion()) {
    155             final EasOptions options = new EasOptions(this);
    156             final int result = options.getProtocolVersionFromServer();
    157             if (result != EasOptions.RESULT_OK) {
    158                 writeResultCode(mValidationResult, result);
    159                 return result;
    160             }
    161             final String protocolVersion = options.getProtocolVersionString();
    162             setProtocolVersion(protocolVersion);
    163             mValidationResult.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION,
    164                     protocolVersion);
    165         }
    166 
    167         // This is intentionally a call to super.performOperation. This is a helper function for
    168         // our version of perfomOperation so calling that function would infinite loop.
    169         final int result = super.performOperation();
    170         writeResultCode(mValidationResult, result);
    171         return result;
    172     }
    173 
    174     /**
    175      * Perform account validation.
    176      * TODO: Remove this function when transition to EasService is complete.
    177      * @return The response {@link Bundle} expected by the RPC.
    178      */
    179     public Bundle doValidate() {
    180         validate();
    181         return mValidationResult;
    182     }
    183 
    184     @Override
    185     protected String getCommand() {
    186         return "FolderSync";
    187     }
    188 
    189     @Override
    190     protected HttpEntity getRequestEntity() throws IOException {
    191         final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
    192         final Serializer s = new Serializer();
    193         s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
    194             .end().end().done();
    195         return makeEntity(s);
    196     }
    197 
    198     @Override
    199     protected int handleResponse(final EasResponse response)
    200             throws IOException, CommandStatusException {
    201         if (!response.isEmpty()) {
    202             new FolderSyncParser(mContext, mContext.getContentResolver(),
    203                     response.getInputStream(), mAccount, mStatusOnly).parse();
    204         }
    205         return RESULT_OK;
    206     }
    207 
    208     @Override
    209     protected boolean handleForbidden() {
    210         return mStatusOnly;
    211     }
    212 
    213     @Override
    214     protected boolean handleProvisionError() {
    215         if (mStatusOnly) {
    216             final EasProvision provisionOperation = new EasProvision(this);
    217             mPolicy = provisionOperation.test();
    218             // Regardless of whether the policy is supported, we return false because there's
    219             // no need to re-run the operation.
    220             return false;
    221         }
    222         return super.handleProvisionError();
    223     }
    224 
    225     /**
    226      * Translate {@link EasOperation} result codes to the values needed by the RPC, and write
    227      * them to the {@link Bundle}.
    228      * @param bundle The {@link Bundle} to return to the RPC.
    229      * @param resultCode The result code for this operation.
    230      */
    231     private void writeResultCode(final Bundle bundle, final int resultCode) {
    232         final int messagingExceptionCode;
    233         switch (resultCode) {
    234             case RESULT_ABORT:
    235                 messagingExceptionCode = MessagingException.IOERROR;
    236                 break;
    237             case RESULT_RESTART:
    238                 messagingExceptionCode = MessagingException.IOERROR;
    239                 break;
    240             case RESULT_TOO_MANY_REDIRECTS:
    241                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    242                 break;
    243             case RESULT_REQUEST_FAILURE:
    244                 messagingExceptionCode = MessagingException.IOERROR;
    245                 break;
    246             case RESULT_FORBIDDEN:
    247                 messagingExceptionCode = MessagingException.ACCESS_DENIED;
    248                 break;
    249             case RESULT_PROVISIONING_ERROR:
    250                 if (mPolicy == null) {
    251                     messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    252                 } else {
    253                     bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
    254                     messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
    255                             MessagingException.SECURITY_POLICIES_REQUIRED :
    256                             MessagingException.SECURITY_POLICIES_UNSUPPORTED;
    257                 }
    258                 break;
    259             case RESULT_AUTHENTICATION_ERROR:
    260                 messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
    261                 break;
    262             case RESULT_CLIENT_CERTIFICATE_REQUIRED:
    263                 messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
    264                 break;
    265             case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
    266                 messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
    267                 break;
    268             case RESULT_OTHER_FAILURE:
    269                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    270                 break;
    271             case RESULT_OK:
    272                 messagingExceptionCode = MessagingException.NO_ERROR;
    273                 break;
    274             default:
    275                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    276                 break;
    277         }
    278         bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
    279     }
    280 }
    281