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      */
EasFolderSync(final Context context, final Account account)71     public EasFolderSync(final Context context, final Account account) {
72         super(context, account);
73         mStatusOnly = false;
74         mPolicy = null;
75     }
76 
makeAccount(final HostAuth hostAuth)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      */
EasFolderSync(final Context context, final HostAuth hostAuth)89     public EasFolderSync(final Context context, final HostAuth hostAuth) {
90         super(context, makeAccount(hostAuth), hostAuth);
91         mStatusOnly = true;
92     }
93 
94     @Override
performOperation()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      */
getValidationResult()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      */
validate()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
getCommand()153     protected String getCommand() {
154         return "FolderSync";
155     }
156 
157     @Override
getRequestEntity()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
handleResponse(final EasResponse response)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
handleForbidden()177     protected boolean handleForbidden() {
178         return mStatusOnly;
179     }
180 
181     @Override
handleProvisionError()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      */
writeResultCode(final Bundle bundle, final int resultCode)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