summaryrefslogtreecommitdiff
path: root/src/com/android/exchange/eas/EasFolderSync.java
blob: 5f81a911a7e56b80b40a17e2c7c0c4ee10f2df17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.exchange.eas;

import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;

import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.exchange.CommandStatusException;
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.FolderSyncParser;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.Tags;
import com.android.mail.utils.LogUtils;

import org.apache.http.HttpEntity;

import java.io.IOException;

/**
 * Implements the EAS FolderSync command. We use this both to actually do a folder sync, and also
 * during account adding flow as a convenient command to validate the account settings (e.g. since
 * it needs to login and will tell us about provisioning requirements).
 * TODO: Doing validation here is kind of wonky. There must be a better way.
 * TODO: Add the use of the Settings command during validation.
 *
 * See http://msdn.microsoft.com/en-us/library/ee237648(v=exchg.80).aspx for more details.
 */
public class EasFolderSync extends EasOperation {

    /** Result code indicating the sync completed correctly. */
    public static final int RESULT_OK = 1;
    /**
     * Result code indicating that this object was constructed for sync and was asked to validate,
     * or vice versa.
     */
    public static final int RESULT_WRONG_OPERATION = 2;

    // TODO: Eliminate the need for mAccount (requires FolderSyncParser changes).
    private final Account mAccount;

    /** Indicates whether this object is for validation rather than sync. */
    private final boolean mStatusOnly;

    /** During validation, this holds the policy we must enforce. */
    private Policy mPolicy;

    /**
     * Constructor for actually doing folder sync.
     * @param context
     * @param account
     */
    public EasFolderSync(final Context context, final Account account) {
        super(context, account);
        mAccount = account;
        mStatusOnly = false;
        mPolicy = null;
    }

    /**
     * Constructor for account validation.
     * @param context
     * @param hostAuth
     */
    public EasFolderSync(final Context context, final HostAuth hostAuth) {
        this(context, new Account(), hostAuth);
    }

    private EasFolderSync(final Context context, final Account account, final HostAuth hostAuth) {
        super(context, account, hostAuth);
        mAccount = account;
        mAccount.mEmailAddress = hostAuth.mLogin;
        mStatusOnly = true;
    }

    /**
     * Perform a folder sync.
     * @param syncResult The {@link SyncResult} object for this sync operation.
     * @return A result code, either from above or from the base class.
     */
    public int doFolderSync(final SyncResult syncResult) {
        if (mStatusOnly) {
            return RESULT_WRONG_OPERATION;
        }
        LogUtils.d(LOG_TAG, "Performing sync for account %d", mAccount.mId);
        return performOperation(syncResult);
    }

    /**
     * Perform account validation.
     * @return The response {@link Bundle} expected by the RPC.
     */
    public Bundle validate() {
        final Bundle bundle = new Bundle(3);
        if (!mStatusOnly) {
            writeResultCode(bundle, RESULT_OTHER_FAILURE);
            return bundle;
        }
        LogUtils.d(LOG_TAG, "Performing validation");

        if (!registerClientCert()) {
            bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE,
                    MessagingException.CLIENT_CERTIFICATE_ERROR);
            return bundle;
        }

        if (shouldGetProtocolVersion()) {
            final EasOptions options = new EasOptions(this);
            final int result = options.getProtocolVersionFromServer(null);
            if (result != EasOptions.RESULT_OK) {
                writeResultCode(bundle, result);
                return bundle;
            }
            final String protocolVersion = options.getProtocolVersionString();
            setProtocolVersion(protocolVersion);
            bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, protocolVersion);
        }

        writeResultCode(bundle, performOperation(null));
        return bundle;
    }

    @Override
    protected String getCommand() {
        return "FolderSync";
    }

    @Override
    protected HttpEntity getRequestEntity() throws IOException {
        final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0";
        final Serializer s = new Serializer();
        s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey)
            .end().end().done();
        return makeEntity(s);
    }

    @Override
    protected int handleResponse(final EasResponse response, final SyncResult syncResult)
            throws IOException {
        if (!response.isEmpty()) {
            try {
                new FolderSyncParser(mContext, mContext.getContentResolver(),
                        response.getInputStream(), mAccount, mStatusOnly).parse();
            } catch (final CommandStatusException e) {
                final int status = e.mStatus;
                LogUtils.d(LOG_TAG, "EasFolderSync.handleResponse status %d", status);
                if (CommandStatusException.CommandStatus.isNeedsProvisioning(status)) {
                    return RESULT_PROVISIONING_ERROR;
                }
                if (CommandStatusException.CommandStatus.isDeniedAccess(status)) {
                    return RESULT_FORBIDDEN;
                }
                return RESULT_OTHER_FAILURE;
            }
        }

        return RESULT_OK;
    }

    @Override
    protected boolean handleForbidden() {
        return mStatusOnly;
    }

    @Override
    protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
        if (mStatusOnly) {
            final EasProvision provisionOperation = new EasProvision(this);
            mPolicy = provisionOperation.test();
            // Regardless of whether the policy is supported, we return false because there's
            // no need to re-run the operation.
            return false;
        }
        return super.handleProvisionError(syncResult, accountId);
    }

    /**
     * Translate {@link EasOperation} result codes to the values needed by the RPC, and write
     * them to the {@link Bundle}.
     * @param bundle The {@link Bundle} to return to the RPC.
     * @param resultCode The result code for this operation.
     */
    private void writeResultCode(final Bundle bundle, final int resultCode) {
        final int messagingExceptionCode;
        switch (resultCode) {
            case RESULT_ABORT:
                messagingExceptionCode = MessagingException.IOERROR;
                break;
            case RESULT_RESTART:
                messagingExceptionCode = MessagingException.IOERROR;
                break;
            case RESULT_TOO_MANY_REDIRECTS:
                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
                break;
            case RESULT_REQUEST_FAILURE:
                messagingExceptionCode = MessagingException.IOERROR;
                break;
            case RESULT_FORBIDDEN:
                messagingExceptionCode = MessagingException.ACCESS_DENIED;
                break;
            case RESULT_PROVISIONING_ERROR:
                if (mPolicy == null) {
                    messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
                } else {
                    bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
                    messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ?
                            MessagingException.SECURITY_POLICIES_REQUIRED :
                            MessagingException.SECURITY_POLICIES_UNSUPPORTED;
                }
                break;
            case RESULT_AUTHENTICATION_ERROR:
                messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED;
                break;
            case RESULT_CLIENT_CERTIFICATE_REQUIRED:
                messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED;
                break;
            case RESULT_PROTOCOL_VERSION_UNSUPPORTED:
                messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED;
                break;
            case RESULT_OTHER_FAILURE:
                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
                break;
            case RESULT_OK:
                messagingExceptionCode = MessagingException.NO_ERROR;
                break;
            default:
                messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION;
                break;
        }
        bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode);
    }
}