• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
EasFolderSync(final Context context, final long accountId)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      */
EasFolderSync(final Context context, final Account account)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      */
EasFolderSync(final Context context, final HostAuth hostAuth)94     public EasFolderSync(final Context context, final HostAuth hostAuth) {
95         super(context, -1);
96         setDummyAccount(hostAuth);
97         mStatusOnly = true;
98     }
99 
100     @Override
performOperation()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      */
getValidationResult()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      */
doFolderSync()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      */
validate()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      */
doValidate()179     public Bundle doValidate() {
180         validate();
181         return mValidationResult;
182     }
183 
184     @Override
getCommand()185     protected String getCommand() {
186         return "FolderSync";
187     }
188 
189     @Override
getRequestEntity()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
handleResponse(final EasResponse response)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
handleForbidden()209     protected boolean handleForbidden() {
210         return mStatusOnly;
211     }
212 
213     @Override
handleProvisionError()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      */
writeResultCode(final Bundle bundle, final int resultCode)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