Improve Exchange AutoDiscover

Clean up the way we attempt multiple different uris, and add
ability to attempt an unauthenticated get request if the first
two alternatives fail. Also, clean up the way that EasOptions
gets its HttpRequest.

Change-Id: Ib89e5e30f4fa6b2144d23ac39f3844099bfba4d0
diff --git a/src/com/android/exchange/eas/EasAutoDiscover.java b/src/com/android/exchange/eas/EasAutoDiscover.java
index feb3866..cbcb032 100644
--- a/src/com/android/exchange/eas/EasAutoDiscover.java
+++ b/src/com/android/exchange/eas/EasAutoDiscover.java
@@ -16,6 +16,7 @@
 
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.entity.StringEntity;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +28,11 @@
 
 public class EasAutoDiscover extends EasOperation {
 
+    public final static int ATTEMPT_PRIMARY = 0;
+    public final static int ATTEMPT_ALTERNATE = 1;
+    public final static int ATTEMPT_UNAUTHENTICATED_GET = 2;
+    public final static int ATTEMPT_MAX = 2;
+
     public final static int RESULT_OK = 1;
     public final static int RESULT_SC_UNAUTHORIZED = RESULT_OP_SPECIFIC_ERROR_RESULT - 0;
     public final static int RESULT_REDIRECT = RESULT_OP_SPECIFIC_ERROR_RESULT - 1;
@@ -55,17 +61,19 @@
     private static final String ELEMENT_NAME_RESPONSE = "Response";
     private static final String ELEMENT_NAME_AUTODISCOVER = "Autodiscover";
 
+    private final int mAttemptNumber;
     private final String mUri;
     private final String mUsername;
     private final String mPassword;
     private HostAuth mHostAuth;
     private String mRedirectUri;
 
-    public EasAutoDiscover(final Context context, final String uri, final String username,
-                           final String password) {
+    public EasAutoDiscover(final Context context, final String uri, final int attemptNumber,
+                           final String username, final String password) {
         // We don't actually need an account or a hostAuth, but the EasServerConnection requires
         // one. Just create dummy values.
         super(context, -1);
+        mAttemptNumber = attemptNumber;
         mUri = uri;
         mUsername = username;
         mPassword = password;
@@ -78,6 +86,25 @@
         setAccount(new Account(), mHostAuth);
     }
 
+    public static String genUri(final String domain, final int attemptNumber) {
+        // Try the following uris in order, as per
+        // http://msdn.microsoft.com/en-us/library/office/jj900169(v=exchg.150).aspx
+        // TODO: That document also describes a fallback strategy to query DNS for an SRV record,
+        // but this would require additional DNS lookup services that are not currently available
+        // in the android platform,
+        switch (attemptNumber) {
+            case ATTEMPT_PRIMARY:
+                return "https://" + domain + AUTO_DISCOVER_PAGE;
+            case ATTEMPT_ALTERNATE:
+                return "https://autodiscover." + domain + AUTO_DISCOVER_PAGE;
+            case ATTEMPT_UNAUTHENTICATED_GET:
+                return "http://autodiscover." + domain + AUTO_DISCOVER_PAGE;
+            default:
+                LogUtils.wtf(TAG, "Illegal attempt number %d", attemptNumber);
+                return null;
+        }
+    }
+
     protected String getRequestUri() {
         return mUri;
     }
@@ -90,14 +117,6 @@
         return login.substring(amp + 1);
     }
 
-    public static String createUri(final String domain) {
-        return "https://" + domain + AUTO_DISCOVER_PAGE;
-    }
-
-    public static String createAlternateUri(final String domain) {
-        return "https://autodiscover." + domain + AUTO_DISCOVER_PAGE;
-    }
-
     @Override
     protected String getCommand() {
         return null;
@@ -129,6 +148,24 @@
         return null;
     }
 
+    /**
+     * Create the request object for this operation.
+     * The default is to use a POST, but some use other request types (e.g. Options).
+     * @return An {@link org.apache.http.client.methods.HttpUriRequest}.
+     * @throws IOException
+     */
+    protected HttpUriRequest makeRequest() throws IOException, MessageInvalidException {
+        final String requestUri = getRequestUri();
+        HttpUriRequest req;
+        if (mAttemptNumber == ATTEMPT_UNAUTHENTICATED_GET) {
+            req = mConnection.makeGet(requestUri);
+        } else {
+            req = mConnection.makePost(requestUri, getRequestEntity(),
+                    getRequestContentType(), addPolicyKeyHeaderToRequest());
+        }
+        return req;
+    }
+
     public String getRedirectUri() {
         return mRedirectUri;
     }
diff --git a/src/com/android/exchange/eas/EasOperation.java b/src/com/android/exchange/eas/EasOperation.java
index 19dc149..174b000 100644
--- a/src/com/android/exchange/eas/EasOperation.java
+++ b/src/com/android/exchange/eas/EasOperation.java
@@ -156,7 +156,7 @@
     protected Account mAccount;
 
     /** The connection to use for this operation. This is created when {@link #mAccount} is set. */
-    private EasServerConnection mConnection;
+    protected EasServerConnection mConnection;
 
     public class MessageInvalidException extends Exception {
         public MessageInvalidException(final String message) {
@@ -487,16 +487,12 @@
 
     /**
      * Create the request object for this operation.
-     * Most operations use a POST, but some use other request types (e.g. Options).
+     * The default is to use a POST, but some use other request types (e.g. Options).
      * @return An {@link HttpUriRequest}.
      * @throws IOException
      */
-    private final HttpUriRequest makeRequest() throws IOException, MessageInvalidException {
+    protected HttpUriRequest makeRequest() throws IOException, MessageInvalidException {
         final String requestUri = getRequestUri();
-        if (requestUri == null) {
-            return mConnection.makeOptions();
-        }
-
         HttpUriRequest req = mConnection.makePost(requestUri, getRequestEntity(),
                 getRequestContentType(), addPolicyKeyHeaderToRequest());
         return req;
diff --git a/src/com/android/exchange/eas/EasOptions.java b/src/com/android/exchange/eas/EasOptions.java
index 131c391..08a01b1 100644
--- a/src/com/android/exchange/eas/EasOptions.java
+++ b/src/com/android/exchange/eas/EasOptions.java
@@ -23,7 +23,9 @@
 
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.HttpUriRequest;
 
+import java.io.IOException;
 import java.util.HashSet;
 
 /**
@@ -102,6 +104,9 @@
         return null;
     }
 
+    protected HttpUriRequest makeRequest() throws IOException, MessageInvalidException {
+        return mConnection.makeOptions();
+    }
     /**
      * Find the best protocol version to use from the header.
      * @param versionHeader The {@link Header} for the server's supported versions.
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index faa092e..87d5b28 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -44,6 +44,7 @@
 
 import org.apache.http.HttpEntity;
 import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpOptions;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpUriRequest;
@@ -313,6 +314,11 @@
         return post;
     }
 
+    public HttpGet makeGet(final String uri) {
+        final HttpGet get = new HttpGet(uri);
+        return get;
+    }
+
     /**
      * Make an {@link HttpOptions} request for this connection.
      * @return The {@link HttpOptions} object.
diff --git a/src/com/android/exchange/service/EasService.java b/src/com/android/exchange/service/EasService.java
index db6c05e..fb56e1c 100644
--- a/src/com/android/exchange/service/EasService.java
+++ b/src/com/android/exchange/service/EasService.java
@@ -153,26 +153,30 @@
         @Override
         public Bundle autoDiscover(final String username, final String password) {
             final String domain = EasAutoDiscover.getDomain(username);
-            final String uri = EasAutoDiscover.createUri(domain);
-            final Bundle result = autoDiscoverInternal(uri, username, password, true);
-            final int resultCode = result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
-            if (resultCode == EasAutoDiscover.RESULT_BAD_RESPONSE) {
-                // Try the alternate uri
-                final String alternateUri = EasAutoDiscover.createAlternateUri(domain);
-                return autoDiscoverInternal(alternateUri, username, password, true);
-            } else {
-                return result;
+            for (int attempt = 0; attempt <= EasAutoDiscover.ATTEMPT_MAX; attempt++) {
+                LogUtils.d(TAG, "autodiscover attempt %d", attempt);
+                final String uri = EasAutoDiscover.genUri(domain, attempt);
+                Bundle result = autoDiscoverInternal(uri, attempt, username, password, true);
+                int resultCode = result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
+                if (resultCode != EasAutoDiscover.RESULT_BAD_RESPONSE) {
+                    return result;
+                } else {
+                    LogUtils.d(TAG, "got BAD_RESPONSE");
+                }
             }
+            return null;
         }
 
-        private Bundle autoDiscoverInternal(final String uri, final String username,
-                                            final String password, final boolean canRetry) {
-            final EasAutoDiscover op = new EasAutoDiscover(EasService.this, uri, username, password);
+        private Bundle autoDiscoverInternal(final String uri, final int attempt,
+                                            final String username, final String password,
+                                            final boolean canRetry) {
+            final EasAutoDiscover op = new EasAutoDiscover(EasService.this, uri, attempt,
+                    username, password);
             final int result = op.performOperation();
             if (result == EasAutoDiscover.RESULT_REDIRECT) {
                 // Try again recursively with the new uri. TODO we should limit the number of redirects.
                 final String redirectUri = op.getRedirectUri();
-                return autoDiscoverInternal(redirectUri, username, password, canRetry);
+                return autoDiscoverInternal(redirectUri, attempt, username, password, canRetry);
             } else if (result == EasAutoDiscover.RESULT_SC_UNAUTHORIZED) {
                 if (canRetry && username.contains("@")) {
                     // Try again using the bare user name
@@ -180,7 +184,7 @@
                     final String bareUsername = username.substring(0, atSignIndex);
                     LogUtils.d(TAG, "%d received; trying username: %s", result, atSignIndex);
                     // Try again recursively, but this time don't allow retries for username.
-                    return autoDiscoverInternal(uri, bareUsername, password, false);
+                    return autoDiscoverInternal(uri, attempt, bareUsername, password, false);
                 } else {
                     // Either we're already on our second try or the username didn't have an "@"
                     // to begin with. Either way, failure.