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