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