Rework EAS account creation & ssl operation

* Fixes 2048663, 2025029, and 2100131
* Add "Trust Certificates" checkbox in EAS account creation
* Use custom ClientConnectionManager for HttpClient with registry
  for plain, ssl, and tssl (trusted ssl) connection
* Use a ConnectionPool for HttpClient connections
* Remove "Domain" checkbox in  EAS account creation
* Remove tests related to the "Domain" field
* TODO Write a test for valid usernames (requires a bit of research)
  <name>, <email address>, <domain>/<name, and <domain>\<name> are all
  valid, but there might be others

Change-Id: I4a0338df5960bfd3d679a88aaf22d1c49f49992b
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ef7b3ba..8151f0c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -364,6 +364,8 @@
     <string name="account_setup_exchange_domain_hint">Enter domain here</string>
     <!-- On "Exchange" setup screen, the use-SSL checkbox label -->
     <string name="account_setup_exchange_ssl_label">Use secure connection (SSL)</string>
+    <!-- On "Exchange" setup screen, the trust ssl certificates checkbox label -->
+    <string name="account_setup_exchange_trust_certificates_label">Accept all SSL certificates</string>
 
     <!-- In Account setup options screen, Activity title -->
     <string name="account_setup_options_title">Account options</string>
diff --git a/src/com/android/exchange/AbstractSyncService.java b/src/com/android/exchange/AbstractSyncService.java
index 15cede8..97cfb87 100644
--- a/src/com/android/exchange/AbstractSyncService.java
+++ b/src/com/android/exchange/AbstractSyncService.java
@@ -101,7 +101,7 @@
      * @throws MessagingException
      */
     public abstract void validateAccount(String host, String userName, String password, int port,
-            boolean ssl, Context context) throws MessagingException;
+            boolean ssl, boolean trustCertificates, Context context) throws MessagingException;
 
     public AbstractSyncService(Context _context, Mailbox _mailbox) {
         mContext = _context;
@@ -129,12 +129,13 @@
      * @throws MessagingException
      */
     static public void validate(Class<? extends AbstractSyncService> klass, String host,
-            String userName, String password, int port, boolean ssl, Context context)
+            String userName, String password, int port, boolean ssl, boolean trustCertificates,
+            Context context)
             throws MessagingException {
         AbstractSyncService svc;
         try {
             svc = klass.newInstance();
-            svc.validateAccount(host, userName, password, port, ssl, context);
+            svc.validateAccount(host, userName, password, port, ssl, trustCertificates, context);
         } catch (IllegalAccessException e) {
             throw new MessagingException("internal error", e);
         } catch (InstantiationException e) {
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index b4f2ac4..7c1e3ae 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -46,6 +46,7 @@
 import org.apache.http.client.methods.HttpOptions;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.params.BasicHttpParams;
@@ -65,11 +66,11 @@
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URLEncoder;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
 public class EasSyncService extends AbstractSyncService {
-
     private static final String EMAIL_WINDOW_SIZE = "5";
     public static final String PIM_WINDOW_SIZE = "20";
     private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
@@ -133,6 +134,7 @@
     public String mUserName;
     public String mPassword;
     private boolean mSsl = true;
+    private boolean mTrustSsl = false; 
     public ContentResolver mContentResolver;
     private String[] mBindArguments = new String[2];
     private ArrayList<String> mPingChangeList;
@@ -149,6 +151,7 @@
         mContentResolver = _context.getContentResolver();
         HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
         mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
+        mTrustSsl = (ha.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES) != 0;
     }
 
     private EasSyncService(String prefix) {
@@ -191,7 +194,7 @@
 
     @Override
     public void validateAccount(String hostAddress, String userName, String password, int port,
-            boolean ssl, Context context) throws MessagingException {
+            boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
         try {
             userLog("Testing EAS: ", hostAddress, ", ", userName, ", ssl = ", ssl ? "1" : "0");
             EasSyncService svc = new EasSyncService("%TestAccount%");
@@ -200,6 +203,7 @@
             svc.mUserName = userName;
             svc.mPassword = password;
             svc.mSsl = ssl;
+            svc.mTrustSsl = trustCertificates;
             svc.mDeviceId = SyncManager.getDeviceId();
             HttpResponse resp = svc.sendHttpClientOptions();
             int code = resp.getStatusLine().getStatusCode();
@@ -218,7 +222,12 @@
                 throw new MessagingException(MessagingException.IOERROR);
             }
         } catch (IOException e) {
-            userLog("IOException caught, reporting I/O error: ", e.getMessage());
+            Throwable cause = e.getCause();
+            if (cause != null && cause instanceof CertificateException) {
+                userLog("CertificateException caught: ", e.getMessage());
+                throw new MessagingException(MessagingException.GENERAL_SECURITY);
+            }
+            userLog("IOException caught: ", e.getMessage());
             throw new MessagingException(MessagingException.IOERROR);
         }
 
@@ -351,7 +360,7 @@
             mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
                     + mDeviceType;
         }
-        String us = (mSsl ? "https" : "http") + "://" + mHostAddress +
+        String us = (mSsl ? (mTrustSsl ? "httpts" : "https") : "http") + "://" + mHostAddress +
             "/Microsoft-Server-ActiveSync";
         if (cmd != null) {
             us += "?Cmd=" + cmd + mCmdString;
@@ -369,11 +378,17 @@
         method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
     }
 
+    private ClientConnectionManager getClientConnectionManager() {
+        return SyncManager.getClientConnectionManager();
+    }
+
     private HttpClient getHttpClient(int timeout) {
         HttpParams params = new BasicHttpParams();
         HttpConnectionParams.setConnectionTimeout(params, 15*SECONDS);
         HttpConnectionParams.setSoTimeout(params, timeout);
-        return new DefaultHttpClient(params);
+        HttpConnectionParams.setSocketBufferSize(params, 8192);
+        HttpClient client = new DefaultHttpClient(getClientConnectionManager(), params);
+        return client;
     }
 
     protected HttpResponse sendHttpClientPost(String cmd, byte[] bytes) throws IOException {
diff --git a/src/com/android/exchange/IEmailService.aidl b/src/com/android/exchange/IEmailService.aidl
index 23ff744..a07fab0 100644
--- a/src/com/android/exchange/IEmailService.aidl
+++ b/src/com/android/exchange/IEmailService.aidl
@@ -21,7 +21,7 @@
 
 interface IEmailService {
     int validate(in String protocol, in String host, in String userName, in String password,
-        int port, boolean ssl) ;
+        int port, boolean ssl, boolean trustCertificates) ;
 
     void startSync(long mailboxId);
     void stopSync(long mailboxId);
diff --git a/src/com/android/exchange/SyncManager.java b/src/com/android/exchange/SyncManager.java
index 96196e6..3c4cb13 100644
--- a/src/com/android/exchange/SyncManager.java
+++ b/src/com/android/exchange/SyncManager.java
@@ -18,6 +18,7 @@
 package com.android.exchange;
 
 import com.android.email.mail.MessagingException;
+import com.android.email.mail.store.TrustManagerFactory;
 import com.android.email.provider.EmailContent;
 import com.android.email.provider.EmailContent.Account;
 import com.android.email.provider.EmailContent.Attachment;
@@ -29,6 +30,19 @@
 import com.android.email.provider.EmailContent.SyncColumns;
 import com.android.exchange.utility.FileLogger;
 
+import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnManagerPNames;
+import org.apache.http.conn.params.ConnPerRoute;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdatedListener;
 import android.app.AlarmManager;
@@ -65,10 +79,16 @@
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
 /**
  * The SyncManager handles all aspects of starting, maintaining, and stopping the various sync
  * adapters used by Exchange.  However, it is capable of handing any kind of email sync, and it
@@ -176,6 +196,8 @@
     private static Thread sServiceThread = null;
     // Cached unique device id
     private static String sDeviceId = null;
+    // ConnectionManager that all EAS threads can use
+    private static ClientConnectionManager sClientConnectionManager = null;
 
     private boolean mStop = false;
 
@@ -240,10 +262,10 @@
     private final IEmailService.Stub mBinder = new IEmailService.Stub() {
 
         public int validate(String protocol, String host, String userName, String password,
-                int port, boolean ssl) throws RemoteException {
+                int port, boolean ssl, boolean trustCertificates) throws RemoteException {
             try {
                 AbstractSyncService.validate(EasSyncService.class, host, userName, password, port,
-                        ssl, SyncManager.this);
+                        ssl, trustCertificates, SyncManager.this);
                 return MessagingException.NO_ERROR;
             } catch (MessagingException e) {
                 return e.getExceptionType();
@@ -672,6 +694,52 @@
         }
     }
 
+    static public ConnPerRoute sConnPerRoute = new ConnPerRoute() {
+        public int getMaxForRoute(HttpRoute route) {
+            return 8;
+        }
+    };
+
+    static public synchronized ClientConnectionManager getClientConnectionManager() {
+        if (sClientConnectionManager == null) {
+            // Create a registry for our three schemes; http and https will use built-in factories
+            SchemeRegistry registry = new SchemeRegistry();
+            registry.register(new Scheme("http",
+                    PlainSocketFactory.getSocketFactory(), 80));
+            registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
+
+            // Create a new SSLSocketFactory for our "trusted ssl"
+            // Get the unsecure trust manager from the factory
+            X509TrustManager trustManager = TrustManagerFactory.get(null, false);
+            TrustManager[] trustManagers = new TrustManager[] {trustManager};
+            SSLContext sslcontext;
+            try {
+                sslcontext = SSLContext.getInstance("TLS");
+                sslcontext.init(null, trustManagers, null);
+                SSLContextImpl sslContext = new SSLContextImpl();
+                try {
+                    sslContext.engineInit(null, trustManagers, null, null, null);
+                } catch (KeyManagementException e) {
+                    throw new AssertionError(e);
+                }
+                // Ok, now make our factory
+                SSLSocketFactory sf = new SSLSocketFactory(sslContext.engineGetSocketFactory());
+                sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+                // Register the httpts scheme with our factory
+                registry.register(new Scheme("httpts", sf, 443));
+                // And create a ccm with our registry
+                HttpParams params = new BasicHttpParams();
+                params.setIntParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 25);
+                params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, sConnPerRoute);
+                sClientConnectionManager = new ThreadSafeClientConnManager(params, registry);
+            } catch (NoSuchAlgorithmException e2) {
+            } catch (KeyManagementException e1) {
+            }
+        }
+        // Null is a valid return result if we get an exception
+        return sClientConnectionManager;
+    }
+
     @Override
     public void onDestroy() {
         log("!!! EAS SyncManager destroyed");