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