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.content.SyncResult;
     21 import android.os.Bundle;
     22 
     23 import com.android.emailcommon.mail.MessagingException;
     24 import com.android.emailcommon.provider.Account;
     25 import com.android.emailcommon.provider.HostAuth;
     26 import com.android.emailcommon.provider.Policy;
     27 import com.android.emailcommon.service.EmailServiceProxy;
     28 import com.android.exchange.CommandStatusException;
     29 import com.android.exchange.EasResponse;
     30 import com.android.exchange.adapter.FolderSyncParser;
     31 import com.android.exchange.adapter.Serializer;
     32 import com.android.exchange.adapter.Tags;
     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     // TODO: Eliminate the need for mAccount (requires FolderSyncParser changes).
     59     private final Account mAccount;
     60 
     61     /** Indicates whether this object is for validation rather than sync. */
     62     private final boolean mStatusOnly;
     63 
     64     /** During validation, this holds the policy we must enforce. */
     65     private Policy mPolicy;
     66 
     67     /**
     68      * Constructor for actually doing folder sync.
     69      * @param context
     70      * @param account
     71      */
     72     public EasFolderSync(final Context context, final Account account) {
     73         super(context, account);
     74         mAccount = account;
     75         mStatusOnly = false;
     76         mPolicy = null;
     77     }
     78 
     79     /**
     80      * Constructor for account validation.
     81      * @param context
     82      * @param hostAuth
     83      */
     84     public EasFolderSync(final Context context, final HostAuth hostAuth) {
     85         this(context, new Account(), hostAuth);
     86     }
     87 
     88     private EasFolderSync(final Context context, final Account account, final HostAuth hostAuth) {
     89         super(context, account, hostAuth);
     90         mAccount = account;
     91         mAccount.mEmailAddress = hostAuth.mLogin;
     92         mStatusOnly = true;
     93     }
     94 
     95     /**
     96      * Perform a folder sync.
     97      * @param syncResult The {@link SyncResult} object for this sync operation.
     98      * @return A result code, either from above or from the base class.
     99      */
    100     public int doFolderSync(final SyncResult syncResult) {
    101         if (mStatusOnly) {
    102             return RESULT_WRONG_OPERATION;
    103         }
    104         LogUtils.d(LOG_TAG, "Performing sync for account %d", mAccount.mId);
    105         return performOperation(syncResult);
    106     }
    107 
    108     /**
    109      * Perform account validation.
    110      * @return The response {@link Bundle} expected by the RPC.
    111      */
    112     public Bundle validate() {
    113         final Bundle bundle = new Bundle(3);
    114         if (!mStatusOnly) {
    115             writeResultCode(bundle, RESULT_OTHER_FAILURE);
    116             return bundle;
    117         }
    118         LogUtils.d(LOG_TAG, "Performing validation");
    119 
    120         if (!registerClientCert()) {
    121             bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
    122                     MessagingException.CLIENT_CERTIFICATE_ERROR);
    123             return bundle;
    124         }
    125 
    126         if (shouldGetProtocolVersion()) {
    127             final EasOptions options = new EasOptions(this);
    128             final int result = options.getProtocolVersionFromServer(null);
    129             if (result != EasOptions.RESULT_OK) {
    130                 writeResultCode(bundle, result);
    131                 return bundle;
    132             }
    133             final String protocolVersion = options.getProtocolVersionString();
    134             setProtocolVersion(protocolVersion);
    135             bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, protocolVersion);
    136         }
    137 
    138         writeResultCode(bundle, performOperation(null));
    139         return bundle;
    140     }
    141 
    142     @Override
    143     protected String getCommand() {
    144         return "FolderSync";
    145     }
    146 
    147     @Override
    148     protected HttpEntity getRequestEntity() throws IOException {
    149         final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
    150         final Serializer s = new Serializer();
    151         s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
    152             .end().end().done();
    153         return makeEntity(s);
    154     }
    155 
    156     @Override
    157     protected int handleResponse(final EasResponse response, final SyncResult syncResult)
    158             throws IOException, CommandStatusException {
    159         if (!response.isEmpty()) {
    160             new FolderSyncParser(mContext, mContext.getContentResolver(),
    161                     response.getInputStream(), mAccount, mStatusOnly).parse();
    162         }
    163         return RESULT_OK;
    164     }
    165 
    166     @Override
    167     protected boolean handleForbidden() {
    168         return mStatusOnly;
    169     }
    170 
    171     @Override
    172     protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
    173         if (mStatusOnly) {
    174             final EasProvision provisionOperation = new EasProvision(this);
    175             mPolicy = provisionOperation.test();
    176             // Regardless of whether the policy is supported, we return false because there's
    177             // no need to re-run the operation.
    178             return false;
    179         }
    180         return super.handleProvisionError(syncResult, accountId);
    181     }
    182 
    183     /**
    184      * Translate {@link EasOperation} result codes to the values needed by the RPC, and write
    185      * them to the {@link Bundle}.
    186      * @param bundle The {@link Bundle} to return to the RPC.
    187      * @param resultCode The result code for this operation.
    188      */
    189     private void writeResultCode(final Bundle bundle, final int resultCode) {
    190         final int messagingExceptionCode;
    191         switch (resultCode) {
    192             case RESULT_ABORT:
    193                 messagingExceptionCode = MessagingException.IOERROR;
    194                 break;
    195             case RESULT_RESTART:
    196                 messagingExceptionCode = MessagingException.IOERROR;
    197                 break;
    198             case RESULT_TOO_MANY_REDIRECTS:
    199                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    200                 break;
    201             case RESULT_REQUEST_FAILURE:
    202                 messagingExceptionCode = MessagingException.IOERROR;
    203                 break;
    204             case RESULT_FORBIDDEN:
    205                 messagingExceptionCode = MessagingException.ACCESS_DENIED;
    206                 break;
    207             case RESULT_PROVISIONING_ERROR:
    208                 if (mPolicy == null) {
    209                     messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    210                 } else {
    211                     bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
    212                     messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
    213                             MessagingException.SECURITY_POLICIES_REQUIRED :
    214                             MessagingException.SECURITY_POLICIES_UNSUPPORTED;
    215                 }
    216                 break;
    217             case RESULT_AUTHENTICATION_ERROR:
    218                 messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
    219                 break;
    220             case RESULT_CLIENT_CERTIFICATE_REQUIRED:
    221                 messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
    222                 break;
    223             case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
    224                 messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
    225                 break;
    226             case RESULT_OTHER_FAILURE:
    227                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    228                 break;
    229             case RESULT_OK:
    230                 messagingExceptionCode = MessagingException.NO_ERROR;
    231                 break;
    232             default:
    233                 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
    234                 break;
    235         }
    236         bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
    237     }
    238 }
    239