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 */ 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 * 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 */ 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 */ 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 */ 179 public Bundle doValidate() { 180 validate(); 181 return mValidationResult; 182 } 183 184 @Override 185 protected String getCommand() { 186 return "FolderSync"; 187 } 188 189 @Override 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 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 209 protected boolean handleForbidden() { 210 return mStatusOnly; 211 } 212 213 @Override 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 */ 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