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.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 // TODO: Eliminate the need for mAccount (requires FolderSyncParser changes). 59 private final Account mAccount; 60 61 /** Indicates whether this object is for validation rather than sync. */ 62 private final boolean mStatusOnly; 63 64 /** During validation, this holds the policy we must enforce. */ 65 private Policy mPolicy; 66 67 /** 68 * Constructor for actually doing folder sync. 69 * @param context 70 * @param account 71 */ 72 public EasFolderSync(final Context context, final Account account) { 73 super(context, account); 74 mAccount = account; 75 mStatusOnly = false; 76 mPolicy = null; 77 } 78 79 /** 80 * Constructor for account validation. 81 * @param context 82 * @param hostAuth 83 */ 84 public EasFolderSync(final Context context, final HostAuth hostAuth) { 85 this(context, new Account(), hostAuth); 86 } 87 88 private EasFolderSync(final Context context, final Account account, final HostAuth hostAuth) { 89 super(context, account, hostAuth); 90 mAccount = account; 91 mAccount.mEmailAddress = hostAuth.mLogin; 92 mStatusOnly = true; 93 } 94 95 /** 96 * Perform a folder sync. 97 * @param syncResult The {@link SyncResult} object for this sync operation. 98 * @return A result code, either from above or from the base class. 99 */ 100 public int doFolderSync(final SyncResult syncResult) { 101 if (mStatusOnly) { 102 return RESULT_WRONG_OPERATION; 103 } 104 LogUtils.d(LOG_TAG, "Performing sync for account %d", mAccount.mId); 105 return performOperation(syncResult); 106 } 107 108 /** 109 * Perform account validation. 110 * @return The response {@link Bundle} expected by the RPC. 111 */ 112 public Bundle validate() { 113 final Bundle bundle = new Bundle(3); 114 if (!mStatusOnly) { 115 writeResultCode(bundle, RESULT_OTHER_FAILURE); 116 return bundle; 117 } 118 LogUtils.d(LOG_TAG, "Performing validation"); 119 120 if (!registerClientCert()) { 121 bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, 122 MessagingException.CLIENT_CERTIFICATE_ERROR); 123 return bundle; 124 } 125 126 if (shouldGetProtocolVersion()) { 127 final EasOptions options = new EasOptions(this); 128 final int result = options.getProtocolVersionFromServer(null); 129 if (result != EasOptions.RESULT_OK) { 130 writeResultCode(bundle, result); 131 return bundle; 132 } 133 final String protocolVersion = options.getProtocolVersionString(); 134 setProtocolVersion(protocolVersion); 135 bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, protocolVersion); 136 } 137 138 writeResultCode(bundle, performOperation(null)); 139 return bundle; 140 } 141 142 @Override 143 protected String getCommand() { 144 return "FolderSync"; 145 } 146 147 @Override 148 protected HttpEntity getRequestEntity() throws IOException { 149 final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0"; 150 final Serializer s = new Serializer(); 151 s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey) 152 .end().end().done(); 153 return makeEntity(s); 154 } 155 156 @Override 157 protected int handleResponse(final EasResponse response, final SyncResult syncResult) 158 throws IOException, CommandStatusException { 159 if (!response.isEmpty()) { 160 new FolderSyncParser(mContext, mContext.getContentResolver(), 161 response.getInputStream(), mAccount, mStatusOnly).parse(); 162 } 163 return RESULT_OK; 164 } 165 166 @Override 167 protected boolean handleForbidden() { 168 return mStatusOnly; 169 } 170 171 @Override 172 protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) { 173 if (mStatusOnly) { 174 final EasProvision provisionOperation = new EasProvision(this); 175 mPolicy = provisionOperation.test(); 176 // Regardless of whether the policy is supported, we return false because there's 177 // no need to re-run the operation. 178 return false; 179 } 180 return super.handleProvisionError(syncResult, accountId); 181 } 182 183 /** 184 * Translate {@link EasOperation} result codes to the values needed by the RPC, and write 185 * them to the {@link Bundle}. 186 * @param bundle The {@link Bundle} to return to the RPC. 187 * @param resultCode The result code for this operation. 188 */ 189 private void writeResultCode(final Bundle bundle, final int resultCode) { 190 final int messagingExceptionCode; 191 switch (resultCode) { 192 case RESULT_ABORT: 193 messagingExceptionCode = MessagingException.IOERROR; 194 break; 195 case RESULT_RESTART: 196 messagingExceptionCode = MessagingException.IOERROR; 197 break; 198 case RESULT_TOO_MANY_REDIRECTS: 199 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION; 200 break; 201 case RESULT_REQUEST_FAILURE: 202 messagingExceptionCode = MessagingException.IOERROR; 203 break; 204 case RESULT_FORBIDDEN: 205 messagingExceptionCode = MessagingException.ACCESS_DENIED; 206 break; 207 case RESULT_PROVISIONING_ERROR: 208 if (mPolicy == null) { 209 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION; 210 } else { 211 bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy); 212 messagingExceptionCode = mPolicy.mProtocolPoliciesUnsupported == null ? 213 MessagingException.SECURITY_POLICIES_REQUIRED : 214 MessagingException.SECURITY_POLICIES_UNSUPPORTED; 215 } 216 break; 217 case RESULT_AUTHENTICATION_ERROR: 218 messagingExceptionCode = MessagingException.AUTHENTICATION_FAILED; 219 break; 220 case RESULT_CLIENT_CERTIFICATE_REQUIRED: 221 messagingExceptionCode = MessagingException.CLIENT_CERTIFICATE_REQUIRED; 222 break; 223 case RESULT_PROTOCOL_VERSION_UNSUPPORTED: 224 messagingExceptionCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED; 225 break; 226 case RESULT_OTHER_FAILURE: 227 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION; 228 break; 229 case RESULT_OK: 230 messagingExceptionCode = MessagingException.NO_ERROR; 231 break; 232 default: 233 messagingExceptionCode = MessagingException.UNSPECIFIED_EXCEPTION; 234 break; 235 } 236 bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, messagingExceptionCode); 237 } 238} 239