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.os.Bundle;
21
22import com.android.emailcommon.mail.MessagingException;
23import com.android.emailcommon.provider.Account;
24import com.android.emailcommon.provider.HostAuth;
25import com.android.emailcommon.provider.Policy;
26import com.android.emailcommon.service.EmailServiceProxy;
27import com.android.exchange.CommandStatusException;
28import com.android.exchange.EasResponse;
29import com.android.exchange.adapter.FolderSyncParser;
30import com.android.exchange.adapter.Serializer;
31import com.android.exchange.adapter.Tags;
32import com.android.exchange.service.EasService;
33import com.android.mail.utils.LogUtils;
34
35import org.apache.http.HttpEntity;
36
37import 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 */
48public 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     * Helper function for {@link #performOperation} -- do some initial checks and, if they pass,
120     * perform a folder sync to verify that we can. This sets {@link #mValidationResult} as a side
121     * effect which holds the result details needed by the UI.
122     * @return A result code, either from above or from the base class.
123     */
124    private int validate() {
125        mValidationResult = new Bundle(3);
126        if (!mStatusOnly) {
127            writeResultCode(mValidationResult, RESULT_OTHER_FAILURE);
128            return RESULT_OTHER_FAILURE;
129        }
130        LogUtils.d(LOG_TAG, "Performing validation");
131
132        if (!registerClientCert()) {
133            mValidationResult.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
134                    MessagingException.CLIENT_CERTIFICATE_ERROR);
135            return RESULT_CLIENT_CERTIFICATE_REQUIRED;
136        }
137
138        if (shouldGetProtocolVersion()) {
139            final EasOptions options = new EasOptions(this);
140            final int result = options.getProtocolVersionFromServer();
141            if (result != EasOptions.RESULT_OK) {
142                writeResultCode(mValidationResult, result);
143                return result;
144            }
145            final String protocolVersion = options.getProtocolVersionString();
146            setProtocolVersion(protocolVersion);
147            mValidationResult.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION,
148                    protocolVersion);
149        }
150
151        // This is intentionally a call to super.performOperation. This is a helper function for
152        // our version of perfomOperation so calling that function would infinite loop.
153        final int result = super.performOperation();
154        writeResultCode(mValidationResult, result);
155        return result;
156    }
157
158    @Override
159    protected String getCommand() {
160        return "FolderSync";
161    }
162
163    @Override
164    protected HttpEntity getRequestEntity() throws IOException {
165        final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
166        final Serializer s = new Serializer();
167        s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
168            .end().end().done();
169        return makeEntity(s);
170    }
171
172    @Override
173    protected int handleResponse(final EasResponse response)
174            throws IOException, CommandStatusException {
175        if (!response.isEmpty()) {
176            new FolderSyncParser(mContext, mContext.getContentResolver(),
177                    response.getInputStream(), mAccount, mStatusOnly).parse();
178        }
179        return RESULT_OK;
180    }
181
182    @Override
183    protected boolean handleForbidden() {
184        return mStatusOnly;
185    }
186
187    @Override
188    protected boolean handleProvisionError() {
189        if (mStatusOnly) {
190            final EasProvision provisionOperation = new EasProvision(this);
191            mPolicy = provisionOperation.test();
192            // Regardless of whether the policy is supported, we return false because there's
193            // no need to re-run the operation.
194            return false;
195        }
196        return super.handleProvisionError();
197    }
198
199    /**
200     * Translate {@link EasOperation} result codes to the values needed by the RPC, and write
201     * them to the {@link Bundle}.
202     * @param bundle The {@link Bundle} to return to the RPC.
203     * @param resultCode The result code for this operation.
204     */
205    private void writeResultCode(final Bundle bundle, final int resultCode) {
206        final int messagingExceptionCode;
207        switch (resultCode) {
208            case RESULT_ABORT:
209                messagingExceptionCode = MessagingException.IOERROR;
210                break;
211            case RESULT_RESTART:
212                messagingExceptionCode = MessagingException.IOERROR;
213                break;
214            case RESULT_TOO_MANY_REDIRECTS:
215                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
216                break;
217            case RESULT_NETWORK_PROBLEM:
218                messagingExceptionCode = MessagingException.IOERROR;
219                break;
220            case RESULT_FORBIDDEN:
221                messagingExceptionCode = MessagingException.ACCESS_DENIED;
222                break;
223            case RESULT_PROVISIONING_ERROR:
224                if (mPolicy == null) {
225                    messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
226                } else {
227                    bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
228                    messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
229                            MessagingException.SECURITY_POLICIES_REQUIRED :
230                            MessagingException.SECURITY_POLICIES_UNSUPPORTED;
231                }
232                break;
233            case RESULT_AUTHENTICATION_ERROR:
234                messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
235                break;
236            case RESULT_CLIENT_CERTIFICATE_REQUIRED:
237                messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
238                break;
239            case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
240                messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
241                break;
242            case RESULT_OTHER_FAILURE:
243                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
244                break;
245            case RESULT_OK:
246                messagingExceptionCode = MessagingException.NO_ERROR;
247                break;
248            default:
249                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
250                break;
251        }
252        bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
253    }
254}
255