Make validation an operation.
This entails also making an operation for getting the
protocol version.
Change-Id: I8b2855cca338e3dbd8b6826b2c428b9e418a675e
diff --git a/src/com/android/exchange/eas/EasFolderSync.java b/src/com/android/exchange/eas/EasFolderSync.java
index 93d6141..28eb77e 100644
--- a/src/com/android/exchange/eas/EasFolderSync.java
+++ b/src/com/android/exchange/eas/EasFolderSync.java
@@ -20,8 +20,11 @@
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;
@@ -38,6 +41,7 @@
* 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.
*/
@@ -57,6 +61,9 @@
/** 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
@@ -66,6 +73,7 @@
super(context, account);
mAccount = account;
mStatusOnly = false;
+ mPolicy = null;
}
/**
@@ -99,17 +107,36 @@
/**
* Perform account validation.
- * TODO: Implement correctly.
- * @param bundle The {@link Bundle} to provide the results of validation to the UI.
- * @return A result code, either from above or from the base class.
+ * @return The response {@link Bundle} expected by the RPC.
*/
- public int validate(final Bundle bundle) {
- if (!mStatusOnly || bundle == null) {
- return RESULT_WRONG_OPERATION;
+ public Bundle validate() {
+ final Bundle bundle = new Bundle(3);
+ if (!mStatusOnly) {
+ writeResultCode(bundle, RESULT_OTHER_FAILURE);
+ return bundle;
}
LogUtils.i(LOG_TAG, "Performing validation");
- final int result = performOperation(null);
- return RESULT_OK;
+
+ 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
@@ -152,4 +179,70 @@
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.SECURITY_POLICIES_REQUIRED;
+ bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, mPolicy);
+ } else {
+ messagingExceptionCode = 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);
+ }
}
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index 6629667..c75e95f 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -17,8 +17,11 @@
package com.android.exchange.eas;
import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.SyncResult;
+import android.net.Uri;
import android.os.Bundle;
import android.text.format.DateUtils;
@@ -26,6 +29,7 @@
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Mailbox;
+import com.android.emailcommon.utility.Utility;
import com.android.exchange.Eas;
import com.android.exchange.EasResponse;
import com.android.exchange.adapter.Serializer;
@@ -33,7 +37,7 @@
import com.android.mail.utils.LogUtils;
import org.apache.http.HttpEntity;
-import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import java.io.IOException;
@@ -78,8 +82,12 @@
public static final int RESULT_PROVISIONING_ERROR = -6;
/** Error code indicating an authentication problem. */
public static final int RESULT_AUTHENTICATION_ERROR = -7;
+ /** Error code indicating the client is missing a certificate. */
+ public static final int RESULT_CLIENT_CERTIFICATE_REQUIRED = -8;
+ /** Error code indicating we don't have a protocol version in common with the server. */
+ public static final int RESULT_PROTOCOL_VERSION_UNSUPPORTED = -9;
/** Error code indicating some other failure. */
- public static final int RESULT_OTHER_FAILURE = -8;
+ public static final int RESULT_OTHER_FAILURE = -10;
protected final Context mContext;
@@ -91,6 +99,13 @@
private final long mAccountId;
private final EasServerConnection mConnection;
+ private EasOperation(final Context context, final long accountId,
+ final EasServerConnection connection) {
+ mContext = context;
+ mAccountId = accountId;
+ mConnection = connection;
+ }
+
protected EasOperation(final Context context, final Account account, final HostAuth hostAuth) {
this(context, account.mId, new EasServerConnection(context, account, hostAuth));
}
@@ -99,11 +114,13 @@
this(context, account, HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv));
}
- protected EasOperation(final Context context, final long accountId,
- final EasServerConnection connection) {
- mContext = context;
- mAccountId = accountId;
- mConnection = connection;
+ /**
+ * This constructor is for use by operations that are created by other operations, e.g.
+ * {@link EasProvision}.
+ * @param parentOperation The {@link EasOperation} that is creating us.
+ */
+ protected EasOperation(final EasOperation parentOperation) {
+ this(parentOperation.mContext, parentOperation.mAccountId, parentOperation.mConnection);
}
/**
@@ -149,12 +166,10 @@
int redirectCount = 0;
do {
- // Perform the POST and handle exceptions.
+ // Perform the HTTP request and handle exceptions.
final EasResponse response;
try {
- final HttpPost post = mConnection.makePost(getRequestUri(), getRequestEntity(),
- getRequestContentType(), addPolicyKeyHeaderToRequest());
- response = mConnection.executePost(post, getTimeout());
+ response = mConnection.executeHttpUriRequest(makeRequest(), getTimeout());
} catch (final IOException e) {
// If we were stopped, return the appropriate result code.
switch (mConnection.getStoppedReason()) {
@@ -173,7 +188,7 @@
return RESULT_REQUEST_FAILURE;
} catch (final IllegalStateException e) {
// Subclasses use ISE to signal a hard error when building the request.
- // TODO: If executePost can throw an ISE, we may want to tidy this up a bit.
+ // TODO: If executeHttpUriRequest can throw an ISE, we may want to tidy this up.
LogUtils.e(LOG_TAG, e, "Exception while sending request");
if (syncResult != null) {
syncResult.databaseError = true;
@@ -232,7 +247,9 @@
if (syncResult != null) {
++syncResult.stats.numAuthExceptions;
}
- handleAuthError();
+ if (response.isMissingCertificate()) {
+ return RESULT_CLIENT_CERTIFICATE_REQUIRED;
+ }
return RESULT_AUTHENTICATION_ERROR;
}
@@ -265,11 +282,42 @@
}
/**
- * Handling for authentication errors. Should be the same for all operations.
- * TODO: Implement.
+ * Reset the protocol version to use for this connection. If it's changed, and our account is
+ * persisted, also write back the changes to the DB.
+ * @param protocolVersion The new protocol version to use, as a string.
*/
- private final void handleAuthError() {
+ protected final void setProtocolVersion(final String protocolVersion) {
+ if (mConnection.setProtocolVersion(protocolVersion) && mAccountId != Account.NOT_SAVED) {
+ final Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, mAccountId);
+ final ContentValues cv = new ContentValues(2);
+ if (getProtocolVersion() >= 12.0) {
+ final int oldFlags = Utility.getFirstRowInt(mContext, uri,
+ Account.ACCOUNT_FLAGS_PROJECTION, null, null, null,
+ Account.ACCOUNT_FLAGS_COLUMN_FLAGS, 0);
+ final int newFlags = oldFlags
+ | Account.FLAGS_SUPPORTS_GLOBAL_SEARCH + Account.FLAGS_SUPPORTS_SEARCH;
+ if (oldFlags != newFlags) {
+ cv.put(EmailContent.AccountColumns.FLAGS, newFlags);
+ }
+ }
+ cv.put(EmailContent.AccountColumns.PROTOCOL_VERSION, protocolVersion);
+ mContext.getContentResolver().update(uri, cv, null, null);
+ }
+ }
+ /**
+ * Create the request object for this operation.
+ * Most operations use a POST, but some use other request types (e.g. Options).
+ * @return An {@link HttpUriRequest}.
+ * @throws IOException
+ */
+ private final HttpUriRequest makeRequest() throws IOException {
+ final String requestUri = getRequestUri();
+ if (requestUri == null) {
+ return mConnection.makeOptions();
+ }
+ return mConnection.makePost(requestUri, getRequestEntity(),
+ getRequestContentType(), addPolicyKeyHeaderToRequest());
}
/**
@@ -286,8 +334,9 @@
protected abstract String getCommand();
/**
- * Build the {@link HttpEntity} which us used to construct the POST. Typically this function
+ * Build the {@link HttpEntity} which is used to construct the POST. Typically this function
* will build the Exchange request using a {@link Serializer} and then call {@link #makeEntity}.
+ * If the subclass is not using a POST, then it should override this to return null.
* @return The {@link HttpEntity} to pass to {@link EasServerConnection#makePost}.
* @throws IOException
*/
@@ -359,7 +408,7 @@
* @return
*/
protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) {
- final EasProvision provisionOperation = new EasProvision(mContext, accountId, mConnection);
+ final EasProvision provisionOperation = new EasProvision(this);
return provisionOperation.provision(syncResult, accountId);
}
@@ -375,6 +424,16 @@
}
/**
+ * Check whether we should ask the server what protocol versions it supports and set this
+ * account to use that version.
+ * @return Whether we need a new protocol version from the server.
+ */
+ protected final boolean shouldGetProtocolVersion() {
+ // TODO: Find conditions under which we should check other than not having one yet.
+ return !mConnection.isProtocolVersionSet();
+ }
+
+ /**
* @return The protocol version to use.
*/
protected final double getProtocolVersion() {
@@ -389,6 +448,13 @@
}
/**
+ * @return Whether we succeeeded in registering the client cert.
+ */
+ protected final boolean registerClientCert() {
+ return mConnection.registerClientCert();
+ }
+
+ /**
* Convenience method for adding a Message to an account's outbox
* @param account The {@link Account} from which to send the message.
* @param msg the message to send
diff --git a/src/com/android/exchange/eas/EasOptions.java b/src/com/android/exchange/eas/EasOptions.java
new file mode 100644
index 0000000..7e44609
--- /dev/null
+++ b/src/com/android/exchange/eas/EasOptions.java
@@ -0,0 +1,124 @@
+/*
+ * 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.SyncResult;
+
+import com.android.exchange.Eas;
+import com.android.exchange.EasResponse;
+import com.android.mail.utils.LogUtils;
+import com.google.common.collect.Sets;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+
+import java.util.HashSet;
+
+/**
+ * Performs an HTTP Options request to the Exchange server, in order to get the protocol
+ * version.
+ */
+public class EasOptions extends EasOperation {
+ private static final String LOG_TAG = "EasOptions";
+
+ /** Result code indicating we successfully got a protocol version. */
+ public static final int RESULT_OK = 1;
+
+ /** Set of Exchange protocol versions we understand. */
+ private static final HashSet<String> SUPPORTED_PROTOCOL_VERSIONS = Sets.newHashSet(
+ Eas.SUPPORTED_PROTOCOL_EX2003,
+ Eas.SUPPORTED_PROTOCOL_EX2007, Eas.SUPPORTED_PROTOCOL_EX2007_SP1,
+ Eas.SUPPORTED_PROTOCOL_EX2010, Eas.SUPPORTED_PROTOCOL_EX2010_SP1);
+
+ private String mProtocolVersion = null;
+
+ public EasOptions(final EasOperation parentOperation) {
+ super(parentOperation);
+ }
+
+ /**
+ * Perform the server request. If successful, callers should use
+ * {@link #getProtocolVersionString} to get the actual protocol version value.
+ * @param syncResult The {@link SyncResult} to use for this operation.
+ * @return A result code; {@link #RESULT_OK} is the only value that indicates success.
+ */
+ public int getProtocolVersionFromServer(final SyncResult syncResult) {
+ return performOperation(syncResult);
+ }
+
+ /**
+ * @return The protocol version to use, or null if we did not successfully get one.
+ */
+ public String getProtocolVersionString() {
+ return mProtocolVersion;
+ }
+
+ @Override
+ protected String getCommand() {
+ return null;
+ }
+
+ @Override
+ protected HttpEntity getRequestEntity() {
+ return null;
+ }
+
+ @Override
+ protected int handleResponse(final EasResponse response, final SyncResult syncResult) {
+ final Header commands = response.getHeader("MS-ASProtocolCommands");
+ final Header versions = response.getHeader("ms-asprotocolversions");
+ final boolean hasProtocolVersion;
+ if (commands == null || versions == null) {
+ LogUtils.e(LOG_TAG, "OPTIONS response without commands or versions");
+ hasProtocolVersion = false;
+ } else {
+ mProtocolVersion = getProtocolVersionFromHeader(versions);
+ hasProtocolVersion = (mProtocolVersion != null);
+ }
+ if (!hasProtocolVersion) {
+ return RESULT_PROTOCOL_VERSION_UNSUPPORTED;
+ }
+
+ return RESULT_OK;
+ }
+
+ @Override
+ protected String getRequestUri() {
+ return null;
+ }
+
+ /**
+ * Find the best protocol version to use from the header.
+ * @param versionHeader The {@link Header} for the server's supported versions.
+ * @return The best protocol version we mutually support, or null if none found.
+ */
+ private String getProtocolVersionFromHeader(final Header versionHeader) {
+ // The string is a comma separated list of EAS versions in ascending order
+ // e.g. 1.0,2.0,2.5,12.0,12.1,14.0,14.1
+ final String supportedVersions = versionHeader.getValue();
+ LogUtils.i(LOG_TAG, "Server supports versions: %s", supportedVersions);
+ final String[] supportedVersionsArray = supportedVersions.split(",");
+ // Find the most recent version we support
+ String newProtocolVersion = null;
+ for (final String version: supportedVersionsArray) {
+ if (SUPPORTED_PROTOCOL_VERSIONS.contains(version)) {
+ newProtocolVersion = version;
+ }
+ }
+ return newProtocolVersion;
+ }
+}
diff --git a/src/com/android/exchange/eas/EasProvision.java b/src/com/android/exchange/eas/EasProvision.java
index 4b6c1eb..80e8a86 100644
--- a/src/com/android/exchange/eas/EasProvision.java
+++ b/src/com/android/exchange/eas/EasProvision.java
@@ -16,7 +16,6 @@
package com.android.exchange.eas;
-import android.content.Context;
import android.content.SyncResult;
import android.os.Build;
@@ -27,7 +26,6 @@
import com.android.exchange.adapter.ProvisionParser;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.Tags;
-import com.android.exchange.service.EasServerConnection;
import com.android.mail.utils.LogUtils;
import org.apache.http.HttpEntity;
@@ -92,9 +90,8 @@
*/
private int mPhase;
- public EasProvision(final Context context, final long accountId,
- final EasServerConnection connection) {
- super(context, accountId, connection);
+ public EasProvision(final EasOperation parentOperation) {
+ super(parentOperation);
mPolicy = null;
mPolicyKey = null;
mStatus = null;
@@ -119,17 +116,22 @@
/**
* Make the provisioning calls to determine if we can handle the required policy.
- * @return Whether we can support the required policy.
+ * @return The {@link Policy} if we support it, or null otherwise.
*/
- public final boolean test() {
+ public final Policy test() {
int result = performInitialRequest(null);
if (result == RESULT_POLICY_UNSUPPORTED) {
// Check if the server will permit partial policies.
result = performAckRequest(null, true);
}
- return result == RESULT_POLICY_SUPPORTED;
+ if (result == RESULT_POLICY_SUPPORTED) {
+ return mPolicy;
+ }
+ return null;
}
+
+
/**
* Get the required policy from the server and enforce it.
* @param syncResult The {@link SyncResult}, if anym for this operation.
diff --git a/src/com/android/exchange/service/EasAccountSyncHandler.java b/src/com/android/exchange/service/EasAccountSyncHandler.java
deleted file mode 100644
index 3bf0b16..0000000
--- a/src/com/android/exchange/service/EasAccountSyncHandler.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.android.exchange.service;
-
-import android.content.Context;
-
-import com.android.emailcommon.provider.Account;
-
-
-/**
- * Performs an Exchange Account sync, which includes folder sync.
- */
-public class EasAccountSyncHandler extends EasAccountValidator {
- public EasAccountSyncHandler(final Context context, final Account account) {
- super(context, account);
- }
-
- public void performSync() {
- doValidationOrSync(null);
- }
-}
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index 72716fc..0ac9a8a 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -1,3 +1,19 @@
+/*
+ * 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.service;
import android.content.ContentResolver;
@@ -28,6 +44,7 @@
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
@@ -36,6 +53,7 @@
import java.io.IOException;
import java.net.URI;
+import java.security.cert.CertificateException;
/**
* Base class for communicating with an EAS server. Anything that needs to send messages to the
@@ -92,17 +110,17 @@
protected final Account mAccount;
private final long mAccountId;
- // Bookkeeping for interrupting a POST. This is primarily for use by Ping (there's currently
+ // Bookkeeping for interrupting a request. This is primarily for use by Ping (there's currently
// no mechanism for stopping a sync).
// Access to these variables should be synchronized on this.
- private HttpPost mPendingPost = null;
+ private HttpUriRequest mPendingRequest = null;
private boolean mStopped = false;
private int mStoppedReason = STOPPED_REASON_NONE;
- /**
- * The protocol version to use, as a double.
- */
+ /** The protocol version to use, as a double. */
private double mProtocolVersion = 0.0d;
+ /** Whether {@link #setProtocolVersion} was last called with a non-null value. */
+ private boolean mProtocolVersionIsSet = false;
/**
* The client for any requests made by this object. This is created lazily, and cleared
@@ -198,12 +216,16 @@
/**
* If a sync causes us to update our protocol version, this function must be called so that
* subsequent calls to {@link #getProtocolVersion()} will do the right thing.
+ * @return Whether the protocol version changed.
*/
- protected void setProtocolVersion(String protocolVersionString) {
+ public boolean setProtocolVersion(String protocolVersionString) {
if (protocolVersionString == null) {
protocolVersionString = Eas.DEFAULT_PROTOCOL_VERSION;
}
+ mProtocolVersionIsSet = (protocolVersionString != null);
+ final double oldProtocolVersion = mProtocolVersion;
mProtocolVersion = Eas.getProtocolVersionDouble(protocolVersionString);
+ return (oldProtocolVersion != mProtocolVersion);
}
/**
@@ -282,6 +304,17 @@
}
/**
+ * Make an {@link HttpOptions} request for this connection.
+ * @return The {@link HttpOptions} object.
+ */
+ public HttpOptions makeOptions() {
+ final HttpOptions method = new HttpOptions(URI.create(makeBaseUriString()));
+ method.setHeader("Authorization", makeAuthString());
+ method.setHeader("User-Agent", getUserAgent());
+ return method;
+ }
+
+ /**
* Send a POST request to the server.
* @param cmd The command we're sending to the server.
* @param entity The {@link HttpEntity} containing the payload of the message.
@@ -331,7 +364,7 @@
if (isPingCommand) {
method.setHeader("Connection", "close");
}
- return executePost(method, timeout);
+ return executeHttpUriRequest(method, timeout);
}
public EasResponse sendHttpClientPost(final String cmd, final byte[] bytes,
@@ -351,7 +384,7 @@
}
/**
- * Executes an {@link HttpPost}.
+ * Executes an {@link HttpUriRequest}.
* Note: this function must not be called by multiple threads concurrently. Only one thread may
* send server requests from a particular object at a time.
* @param method The post to execute.
@@ -359,7 +392,7 @@
* @return The response from the Exchange server.
* @throws IOException
*/
- public EasResponse executePost(final HttpPost method, final long timeout)
+ public EasResponse executeHttpUriRequest(final HttpUriRequest method, final long timeout)
throws IOException {
// The synchronized blocks are here to support the stop() function, specifically to handle
// when stop() is called first. Notably, they are NOT here in order to guard against
@@ -372,7 +405,7 @@
// callers can equate IOException with "this POST got killed for some reason".
throw new IOException("Command was stopped before POST");
}
- mPendingPost = method;
+ mPendingRequest = method;
}
boolean postCompleted = false;
try {
@@ -382,7 +415,7 @@
return response;
} finally {
synchronized (this) {
- mPendingPost = null;
+ mPendingRequest = null;
if (postCompleted) {
mStoppedReason = STOPPED_REASON_NONE;
}
@@ -391,7 +424,7 @@
}
protected EasResponse executePost(final HttpPost method) throws IOException {
- return executePost(method, COMMAND_TIMEOUT);
+ return executeHttpUriRequest(method, COMMAND_TIMEOUT);
}
/**
@@ -407,11 +440,11 @@
public synchronized void stop(final int reason) {
// Only process legitimate reasons.
if (reason >= STOPPED_REASON_ABORT && reason <= STOPPED_REASON_RESTART) {
- final boolean isMidPost = (mPendingPost != null);
+ final boolean isMidPost = (mPendingRequest != null);
LogUtils.i(TAG, "%s with reason %d", (isMidPost ? "Interrupt" : "Stop next"), reason);
mStoppedReason = reason;
if (isMidPost) {
- mPendingPost.abort();
+ mPendingRequest.abort();
} else {
mStopped = true;
}
@@ -428,6 +461,30 @@
}
/**
+ * Try to register our client certificate, if needed.
+ * @return True if we succeeded or didn't need a client cert, false if we failed to register it.
+ */
+ public boolean registerClientCert() {
+ if (mHostAuth.mClientCertAlias != null) {
+ try {
+ getClientConnectionManager().registerClientCert(mContext, mHostAuth);
+ } catch (final CertificateException e) {
+ // The client certificate the user specified is invalid/inaccessible.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return Whether {@link #setProtocolVersion} was last called with a non-null value. Note that
+ * at construction time it is set to whatever protocol version is in the account.
+ */
+ public boolean isProtocolVersionSet() {
+ return mProtocolVersionIsSet;
+ }
+
+ /**
* Convenience method for adding a Message to an account's outbox
* @param account The {@link Account} from which to send the message.
* @param msg The message to send
diff --git a/src/com/android/exchange/service/EmailSyncAdapterService.java b/src/com/android/exchange/service/EmailSyncAdapterService.java
index 29e5d6b..c8b1d5a 100644
--- a/src/com/android/exchange/service/EmailSyncAdapterService.java
+++ b/src/com/android/exchange/service/EmailSyncAdapterService.java
@@ -254,7 +254,7 @@
@Override
public Bundle validate(final HostAuth hostAuth) {
LogUtils.d(TAG, "IEmailService.validate");
- return new EasAccountValidator(EmailSyncAdapterService.this, hostAuth).validate();
+ return new EasFolderSync(EmailSyncAdapterService.this, hostAuth).validate();
}
@Override