Merge "Add telephony side visual voicemail config" into nyc-mr1-dev
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index f452bab..7d5cc20 100644
--- a/src/com/android/phone/common/mail/MailTransport.java
+++ b/src/com/android/phone/common/mail/MailTransport.java
@@ -32,7 +32,6 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -66,6 +65,7 @@
     private BufferedOutputStream mOut;
     private final int mFlags;
     private SocketCreator mSocketCreator;
+    private InetSocketAddress mAddress;
 
     public MailTransport(Context context, ImapHelper imapHelper, Network network, String address,
             int port, int flags) {
@@ -126,29 +126,21 @@
         while (socketAddresses.size() > 0) {
             mSocket = createSocket();
             try {
-                InetSocketAddress address = socketAddresses.remove(0);
-                mSocket.connect(address, SOCKET_CONNECT_TIMEOUT);
+                mAddress = socketAddresses.remove(0);
+                mSocket.connect(mAddress, SOCKET_CONNECT_TIMEOUT);
 
                 if (canTrySslSecurity()) {
-                    /**
-                     * {@link SSLSocket} must connect in its constructor, or create through a
-                     * already connected socket. Since we need to use
-                     * {@link Socket#connect(SocketAddress, int) } to set timeout, we can only
-                     * create it here.
+                    /*
+                    SSLSocket cannot be created with a connection timeout, so instead of doing a
+                    direct SSL connection, we connect with a normal connection and upgrade it into
+                    SSL
                      */
-                    LogUtils.d(TAG, "open: converting to SSL socket");
-                    mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
-                            .createSocket(mSocket, address.getHostName(), address.getPort(), true);
-                    // After the socket connects to an SSL server, confirm that the hostname is as
-                    // expected
-                    if (!canTrustAllCertificates()) {
-                        verifyHostname(mSocket, mHost);
-                    }
+                    reopenTls();
+                } else {
+                    mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+                    mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+                    mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
                 }
-
-                mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
-                mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
-                mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
                 success = true;
                 return;
             } catch (IOException ioe) {
@@ -205,6 +197,32 @@
     }
 
     /**
+     * Attempts to reopen a normal connection into a TLS connection.
+     */
+    public void reopenTls() throws MessagingException {
+        try {
+            LogUtils.d(TAG, "open: converting to TLS socket");
+            mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
+                    .createSocket(mSocket, mAddress.getHostName(), mAddress.getPort(), true);
+            // After the socket connects to an SSL server, confirm that the hostname is as
+            // expected
+            if (!canTrustAllCertificates()) {
+                verifyHostname(mSocket, mHost);
+            }
+            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
+            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+
+        } catch (SSLException e) {
+            LogUtils.d(TAG, e.toString());
+            throw new CertificateValidationException(e.getMessage(), e);
+        } catch (IOException ioe) {
+            LogUtils.d(TAG, ioe.toString());
+            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
+        }
+    }
+
+    /**
      * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
      * service but is not in the public API.
      *
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index 9207aa9..58f0f76 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -17,21 +17,23 @@
 
 import android.provider.VoicemailContract.Status;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.phone.common.mail.AuthenticationFailedException;
 import com.android.phone.common.mail.CertificateValidationException;
 import com.android.phone.common.mail.MailTransport;
 import com.android.phone.common.mail.MessagingException;
+import com.android.phone.common.mail.store.ImapStore.ImapException;
 import com.android.phone.common.mail.store.imap.ImapConstants;
 import com.android.phone.common.mail.store.imap.ImapResponse;
 import com.android.phone.common.mail.store.imap.ImapResponseParser;
 import com.android.phone.common.mail.store.imap.ImapUtility;
 import com.android.phone.common.mail.utils.LogUtils;
-import com.android.phone.common.mail.store.ImapStore.ImapException;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLException;
@@ -46,6 +48,7 @@
     private ImapStore mImapStore;
     private MailTransport mTransport;
     private ImapResponseParser mParser;
+    private Set<String> mCapabilities = new ArraySet<>();
 
     static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
 
@@ -102,6 +105,22 @@
 
             createParser();
 
+            // The server should greet us with something like
+            // * OK IMAP4rev1 Server
+            // consume the response before doing anything else.
+            ImapResponse response = mParser.readResponse();
+            if (!response.isOk()) {
+                mImapStore.getImapHelper()
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+                throw new MessagingException(
+                        MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
+                        "Invalid server initial response");
+            }
+
+            queryCapability();
+
+            maybeDoStartTls();
+
             // LOGIN
             doLogin();
         } catch (SSLException e) {
@@ -133,6 +152,21 @@
     }
 
     /**
+     * Attempts to convert the connection into secure connection.
+     */
+    private void maybeDoStartTls() throws IOException, MessagingException {
+        // STARTTLS is required in the OMTP standard but not every implementation support it.
+        // Make sure the server does have this capability
+        if (hasCapability(ImapConstants.CAPABILITY_STARTTLS)) {
+            executeSimpleCommand(ImapConstants.STARTTLS);
+            mTransport.reopenTls();
+            createParser();
+            // The cached capabilities should be refreshed after TLS is established.
+            queryCapability();
+        }
+    }
+
+    /**
      * Logs into the IMAP server
      */
     private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
@@ -157,6 +191,24 @@
         }
     }
 
+    private void queryCapability() throws IOException, MessagingException {
+        List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
+        mCapabilities.clear();
+        for (ImapResponse response : responses) {
+            if (response.isTagged()) {
+                continue;
+            }
+            for (int i = 0; i < response.size(); i++) {
+                mCapabilities.add(response.getStringOrEmpty(i).getString());
+            }
+        }
+
+        LogUtils.d(TAG, "Capabilities: " + mCapabilities.toString());
+    }
+
+    private boolean hasCapability(String capability) {
+        return mCapabilities.contains(capability);
+    }
     /**
      * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
      * set it to {@link #mParser}.
diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
index 63dda8c..a04d584 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
@@ -109,4 +109,9 @@
     public static final String EXPIRED = "EXPIRED";
     public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
     public static final String UNAVAILABLE = "UNAVAILABLE";
+
+    /**
+     * capabilities
+     */
+    public static final String CAPABILITY_STARTTLS = "STARTTLS";
 }
\ No newline at end of file