Merge "Update touch to tap in display strings."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a75e46d..fa0a3d7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -503,7 +503,12 @@
         </service>
 
         <!-- service to dump telephony information -->
-        <service android:name="TelephonyDebugService" />
+        <service android:name="com.android.phone.TelephonyDebugService"
+                 android:permission="android.permission.DUMP">
+            <intent-filter>
+                <action android:name="com.android.phone.TelephonyDebugService" />
+            </intent-filter>
+        </service>
 
         <activity android:name="EmergencyCallbackModeExitDialog"
             android:excludeFromRecents="true"
@@ -663,6 +668,13 @@
                    android:mimeType="vnd.android.cursor.item/voicemail" />
           </intent-filter>
        </receiver>
+        <receiver
+            android:name="com.android.phone.vvm.omtp.sync.OmtpVvmSyncReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SYNC_VOICEMAIL"/>
+            </intent-filter>
+        </receiver>
        <receiver
            android:name="com.android.phone.vvm.omtp.sync.VoicemailProviderChangeReceiver"
            android:exported="true">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a9e58ea..1f65c3d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -983,6 +983,8 @@
     <string name="incall_error_supp_service_reject">Can\'t reject call.</string>
     <!-- In-call screen: message displayed in an error dialog -->
     <string name="incall_error_supp_service_hangup">Can\'t release call(s).</string>
+    <!-- In-call screen: message displayed in an error dialog -->
+    <string name="incall_error_supp_service_hold">Can\'t hold calls.</string>
     <!-- In-call screen: call failure message displayed in an error dialog when WFC is enabled, is wifi-only, and not connected to a wireless network. [CHAR_LIMIT=NONE] -->
     <string name="incall_error_wfc_only_no_wireless_network">Connect to a wireless network to make a call.</string>
 
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
index cfffb94..7836248 100644
--- a/src/com/android/phone/CallNotifier.java
+++ b/src/com/android/phone/CallNotifier.java
@@ -570,6 +570,9 @@
             if (DBG) log("onSuppServiceFailed: displaying merge failure message");
             mergeFailedString = mApplication.getResources().getString(
                     R.string.incall_error_supp_service_switch);
+        } else if (r.result == Phone.SuppService.HOLD) {
+            mergeFailedString = mApplication.getResources().getString(
+                    R.string.incall_error_supp_service_hold);
         }
         PhoneDisplayMessage.displayErrorMessage(mApplication, mergeFailedString);
 
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index e8a6e8b..59d8de7 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -202,7 +202,7 @@
         CarrierConfigManager configMgr =
                 (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE);
         PersistableBundle carrierConfig =
-                configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubId());
+                configMgr.getConfigForSubId(SubscriptionManager.getDefaultVoiceSubscriptionId());
         if (carrierConfig.getBoolean(CarrierConfigManager.KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL)) {
             mDialButton.setOnClickListener(this);
         } else {
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index fd42f6f..026e798 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -438,7 +438,7 @@
     }
 
     public PersistableBundle getCarrierConfig() {
-        return getCarrierConfigForSubId(SubscriptionManager.getDefaultSubId());
+        return getCarrierConfigForSubId(SubscriptionManager.getDefaultSubscriptionId());
     }
 
     public PersistableBundle getCarrierConfigForSubId(int subId) {
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 4b22e57..63256b6 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -740,6 +740,10 @@
                             loge("queryModemActivityInfo: Unknown exception");
                         }
                     }
+                    // Result cannot be null. Return ModemActivityInfo with all fields set to 0.
+                    if (request.result == null) {
+                        request.result = new ModemActivityInfo(0, 0, 0, null, 0, 0);
+                    }
                     synchronized (request) {
                         request.notifyAll();
                     }
diff --git a/src/com/android/phone/TelephonyDebugService.java b/src/com/android/phone/TelephonyDebugService.java
index 04ebec2..8ec76a2 100644
--- a/src/com/android/phone/TelephonyDebugService.java
+++ b/src/com/android/phone/TelephonyDebugService.java
@@ -18,22 +18,20 @@
 
 import com.android.internal.telephony.DebugService;
 import com.android.internal.telephony.ITelephonyDebug;
-import com.android.internal.telephony.TelephonyEventLog;
+import com.android.internal.telephony.ITelephonyDebugSubscriber;
+import com.android.internal.telephony.TelephonyEvent;
 
 import android.app.Service;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Formatter;
 import java.util.List;
-import java.util.Locale;
-
-import static com.android.internal.telephony.RILConstants.*;
 
 /**
  * A debug service for telephony.
@@ -44,52 +42,6 @@
     private static final boolean VDBG = true;
     private DebugService mDebugService = new DebugService();
 
-    public static final String JSON_KEY_TAG = "tag";
-    public static final String JSON_KEY_REG_STATE = "reg-state";
-    public static final String JSON_KEY_DATA_REG_STATE = "data-reg-state";
-    public static final String JSON_KEY_ROAMING_TYPE = "roaming-type";
-    public static final String JSON_KEY_DATA_ROAMING_TYPE = "data-roaming-type";
-    public static final String JSON_KEY_OPERATOR_ALPHA_LONG = "operator-alpha-long";
-    public static final String JSON_KEY_OPERATOR_ALPHA_SHORT = "operator-alpha-short";
-    public static final String JSON_KEY_OPERATOR_NUMERIC = "operator-numeric";
-    public static final String JSON_KEY_DATA_OPERATOR_ALPHA_LONG = "data-operator-alpha-long";
-    public static final String JSON_KEY_DATA_OPERATOR_ALPHA_SHORT = "data-operator-alpha-short";
-    public static final String JSON_KEY_DATA_OPERATOR_NUMERIC = "data-operator-numeric";
-    public static final String JSON_KEY_RAT = "rat";
-    public static final String JSON_KEY_DATA_RAT = "data-rat";
-    public static final String JSON_KEY_STATE = "state";
-    public static final String JSON_KEY_REASON_INFO = "reason_info";
-    public static final String JSON_KEY_REASON_INFO_CODE = "code";
-    public static final String JSON_KEY_REASON_INFO_EXTRA_CODE = "extra_code";
-    public static final String JSON_KEY_REASON_INFO_EXTRA_MESSAGE = "extra_message";
-    public static final String JSON_KEY_VOLTE = "VoLTE";
-    public static final String JSON_KEY_VILTE = "ViLTE";
-    public static final String JSON_KEY_VOWIFI = "VoWiFi";
-    public static final String JSON_KEY_VIWIFI = "ViWiFi";
-    public static final String JSON_KEY_UTLTE = "UTLTE";
-    public static final String JSON_KEY_UTWIFI = "UTWiFi";
-    public static final String JSON_KEY_DATA_CALLS = "data-calls";
-    public static final String JSON_KEY_STATUS = "status";
-    public static final String JSON_KEY_CID = "cid";
-    public static final String JSON_KEY_ACTIVE = "active";
-    public static final String JSON_KEY_TYPE = "type";
-    public static final String JSON_KEY_IFNAME = "ifname";
-    public static final String JSON_KEY_SERIAL = "serial";
-    public static final String JSON_KEY_PROFILE = "profile";
-    public static final String JSON_KEY_APN = "apn";
-    public static final String JSON_KEY_PROTOCOL = "protocol";
-    public static final String JSON_KEY_REASON = "reason";
-    public static final String JSON_KEY_CLIR_MODE = "clirMode";
-    public static final String JSON_KEY_EVT = "evt";
-    public static final String JSON_KEY_GSM_INDEX = "gsmIndex";
-    public static final String JSON_KEY_RETRY = "retry";
-    public static final String JSON_KEY_SMS_MESSAGE_REF = "messageRef";
-    public static final String JSON_KEY_SMS_ERROR_CODE = "errorCode";
-    public static final String JSON_KEY_RIL_ERROR = "error";
-    public static final String JSON_KEY_CALL_ID = "call-id";
-    public static final String JSON_KEY_SRC_TECH = "src-tech";
-    public static final String JSON_KEY_TARGET_TECH = "target-tech";
-
     /** Constructor */
     public TelephonyDebugService() {
         if (DBG) Log.d(TAG, "TelephonyDebugService()");
@@ -106,489 +58,82 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        boolean dumpEvents = false;
-        if (args != null) {
-            for (String arg : args) {
-                if ("--events".equals(arg)) {
-                    dumpEvents = true;
-                } else if ("--reset-events".equals(arg)) {
-                    synchronized (mEvents) {
-                        mEvents.clear();
-                    }
-                    pw.println("TelephonyDebugService reset.");
-                    return;
-                } else if ("-h".equals(arg)) {
-                    dumpHelp(pw);
-                    return;
-                } else {
-                    pw.println("Unknown option: " + arg);
-                    dumpHelp(pw);
-                    return;
-                }
-            }
-        }
-
-        if (dumpEvents) {
-            synchronized (mEvents) {
-                pw.println("{\"version\": \"1.0\"," +
-                        "\"events\": [");
-                for (Event e : mEvents) {
-                    pw.println(e.toJson());
-                }
-                pw.println("]}");
-            }
-        } else {
-            mDebugService.dump(fd, pw, args);
-        }
+        mDebugService.dump(fd, pw, args);
     }
 
-    private static void dumpHelp(PrintWriter pw) {
-        pw.println("TelephonyDebugService dump options:");
-        pw.println("  [--events] [--reset-events] [-h]");
-        pw.println("  --events: dump events in JSON format.");
-        pw.println("  --reset-events: reset the stats, clearing all current data.");
-        pw.println("  -h: print this help text.");
-    }
-
-    class Event {
-
-        public static final String JSON_TAG_SETTINGS = "SETTINGS";
-        public static final String JSON_TAG_SERVICE_STATE = "SERVICE_STATE";
-        public static final String JSON_TAG_IMS_CONNECTION_STATE = "IMS_CONNECTION_STATE";
-        public static final String JSON_TAG_IMS_CAPABILITIES = "IMS_CAPABILITIES";
-        public static final String JSON_TAG_DATA_CALL_LIST = "DATA_CALL_LIST";
-        public static final String JSON_TAG_RIL_REQUEST_SETUP_DATA_CALL
-                = "RIL_REQUEST_SETUP_DATA_CALL";
-        public static final String JSON_TAG_RIL_REQUEST_DEACTIVATE_DATA_CALL
-                = "RIL_REQUEST_DEACTIVATE_DATA_CALL";
-        public static final String JSON_TAG_RIL_REQUEST_DIAL = "RIL_REQUEST_DIAL";
-        public static final String JSON_TAG_RIL_REQUEST_HANGUP = "RIL_REQUEST_HANGUP";
-        public static final String JSON_TAG_RIL_REQUEST_ANSWER = "RIL_REQUEST_ANSWER";
-        public static final String JSON_TAG_RIL_REQUEST_SEND_SMS = "RIL_REQUEST_SEND_SMS";
-        public static final String JSON_TAG_RIL_RESPONSE_SETUP_DATA_CALL
-                = "RIL_RESPONSE_SETUP_DATA_CALL";
-        public static final String JSON_TAG_RIL_UNSOL_CALL_RING = "RIL_UNSOL_CALL_RING";
-        public static final String JSON_TAG_RIL_UNSOL_SRVCC_STATE_NOTIFY
-                = "RIL_UNSOL_SRVCC_STATE_NOTIFY";
-        public static final String JSON_TAG_RIL_UNSOL_RESPONSE_NEW_SMS
-                = "RIL_UNSOL_RESPONSE_NEW_SMS";
-        public static final String JSON_TAG_RIL_UNSOL_RESPONSE_CDMA_NEW_SMS
-                = "RIL_UNSOL_RESPONSE_CDMA_NEW_SMS";
-        public static final String JSON_TAG_IMS_CALL = "IMS_CALL";
-        public static final String JSON_TAG_IMS_CALL_HANDOVER = "IMS_CALL_HANDOVER";
-        public static final String JSON_TAG_IMS_CALL_STATE = "IMS_CALL_STATE";
-        public static final String JSON_TAG_PHONE_STATE = "PHONE_STATE";
-        public static final String JSON_TAG_SMS = "SMS";
-
-        public long timestamp;
-        public int phoneId;
-        public int tag;
-        public int param1;
-        public int param2;
-        public Bundle data;
-
-        public Event(long timestamp, int phoneId, int tag, int param1, int param2, Bundle data) {
-            this.timestamp = timestamp;
-            this.phoneId = phoneId;
-            this.tag = tag;
-            this.param1 = param1;
-            this.param2 = param2;
-            this.data = data;
-        }
-
-        public String imsCallEventToString(int evt) {
-            switch (evt) {
-                case TelephonyEventLog.TAG_IMS_CALL_START: return "START";
-                case TelephonyEventLog.TAG_IMS_CALL_START_CONFERENCE: return "START_CONFERENCE";
-                case TelephonyEventLog.TAG_IMS_CALL_RECEIVE: return "RECEIVE";
-                case TelephonyEventLog.TAG_IMS_CALL_ACCEPT: return "ACCEPT";
-                case TelephonyEventLog.TAG_IMS_CALL_REJECT: return "REJECT";
-                case TelephonyEventLog.TAG_IMS_CALL_TERMINATE: return "TERMINATE";
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD: return "HOLD";
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME: return "RESUME";
-                case TelephonyEventLog.TAG_IMS_CALL_MERGE: return "MERGE";
-                case TelephonyEventLog.TAG_IMS_CALL_UPDATE: return "UPDATE";
-                case TelephonyEventLog.TAG_IMS_CALL_PROGRESSING: return "PROGRESSING";
-                case TelephonyEventLog.TAG_IMS_CALL_STARTED: return "STARTED";
-                case TelephonyEventLog.TAG_IMS_CALL_START_FAILED: return "START_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_TERMINATED: return "TERMINATED";
-                case TelephonyEventLog.TAG_IMS_CALL_HELD: return "HELD";
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD_FAILED: return "HOLD_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD_RECEIVED: return "HOLD_RECEIVED";
-                case TelephonyEventLog.TAG_IMS_CALL_RESUMED: return "RESUMED";
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME_FAILED: return "RESUME_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME_RECEIVED: return "RESUME_RECEIVED";
-                case TelephonyEventLog.TAG_IMS_CALL_UPDATED: return "UPDATED";
-                case TelephonyEventLog.TAG_IMS_CALL_UPDATE_FAILED: return "UPDATE_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_MERGED: return "MERGED";
-                case TelephonyEventLog.TAG_IMS_CALL_MERGE_FAILED: return "MERGE_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_HANDOVER: return "HANDOVER";
-                case TelephonyEventLog.TAG_IMS_CALL_HANDOVER_FAILED: return "HANDOVER_FAILED";
-                case TelephonyEventLog.TAG_IMS_CALL_TTY_MODE_RECEIVED: return "TTY_MODE_RECEIVED";
-                case TelephonyEventLog.TAG_IMS_CONFERENCE_PARTICIPANTS_STATE_CHANGED:
-                    return "CONFERENCE_PARTICIPANTS_STATE_CHANGED";
-                case TelephonyEventLog.TAG_IMS_MULTIPARTY_STATE_CHANGED:
-                    return "MULTIPARTY_STATE_CHANGED";
-                case TelephonyEventLog.TAG_IMS_CALL_STATE: return "STATE";
-            }
-            return "UNKNOWN("+evt+")";
-        }
-
-        public String rilResponseToString(int evt) {
-            switch (evt) {
-                case RIL_REQUEST_DEACTIVATE_DATA_CALL: return "RIL_RESPONSE_DEACTIVATE_DATA_CALL";
-                case RIL_REQUEST_HANGUP: return "RIL_RESPONSE_HANGUP";
-                case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND: return "RIL_RESPONSE_HANGUP_WAITING_OR_BACKGROUND";
-                case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND: return "RIL_RESPONSE_HANGUP_FOREGROUND_RESUME_BACKGROUND";
-                case RIL_REQUEST_DIAL: return "RIL_RESPONSE_DIAL";
-                case RIL_REQUEST_ANSWER: return "RIL_RESPONSE_ANSWER";
-                case RIL_REQUEST_SEND_SMS: return "RIL_RESPONSE_SEND_SMS";
-                case RIL_REQUEST_SEND_SMS_EXPECT_MORE: return "RIL_RESPONSE_SEND_SMS_EXPECT_MORE";
-                case RIL_REQUEST_CDMA_SEND_SMS: return "RIL_RESPONSE_CDMA_SEND_SMS";
-                case RIL_REQUEST_IMS_SEND_SMS: return "RIL_RESPONSE_IMS_SEND_SMS";
-            }
-            return "UNKNOWN("+evt+")";
-        }
-
-        public String toString() {
-            return String.format("%d,%d,%d,%d,%d,%s",
-                    timestamp, phoneId, tag, param1, param2, data);
-        }
-
-        public String toJson() {
-            StringBuilder sb = new StringBuilder();
-            Formatter formatter = new Formatter(sb, Locale.US);
-            formatter.format("{\"ts\":%d, \"phone\":%d", timestamp, phoneId);
-            switch (tag) {
-                case TelephonyEventLog.TAG_SETTINGS:
-                    formatter.format(", \"%s\":\"%s\"", JSON_KEY_TAG, JSON_TAG_SETTINGS);
-                    break;
-
-                case TelephonyEventLog.TAG_SERVICE_STATE:
-                    serviceStateToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_IMS_CONNECTION_STATE:
-                    imsConnectionStateToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_IMS_CAPABILITIES:
-                    imsCapabilitiesToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_DATA_CALL_LIST:
-                    dataCallListToJson(sb, formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_RIL_REQUEST:
-                    rilRequestToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_RIL_RESPONSE:
-                    rilResponseToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_RIL_UNSOL_RESPONSE:
-                    unsolRilResponseToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_IMS_CALL_START:
-                case TelephonyEventLog.TAG_IMS_CALL_START_CONFERENCE:
-                case TelephonyEventLog.TAG_IMS_CALL_RECEIVE:
-                case TelephonyEventLog.TAG_IMS_CALL_ACCEPT:
-                case TelephonyEventLog.TAG_IMS_CALL_REJECT:
-                case TelephonyEventLog.TAG_IMS_CALL_TERMINATE:
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD:
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME:
-                case TelephonyEventLog.TAG_IMS_CALL_PROGRESSING:
-                case TelephonyEventLog.TAG_IMS_CALL_STARTED:
-                case TelephonyEventLog.TAG_IMS_CALL_START_FAILED:
-                case TelephonyEventLog.TAG_IMS_CALL_TERMINATED:
-                case TelephonyEventLog.TAG_IMS_CALL_HELD:
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD_RECEIVED:
-                case TelephonyEventLog.TAG_IMS_CALL_HOLD_FAILED:
-                case TelephonyEventLog.TAG_IMS_CALL_RESUMED:
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME_RECEIVED:
-                case TelephonyEventLog.TAG_IMS_CALL_RESUME_FAILED:
-                    imsCallEventToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_IMS_CALL_HANDOVER:
-                case TelephonyEventLog.TAG_IMS_CALL_HANDOVER_FAILED:
-                    imsHandoverToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_IMS_CALL_STATE:
-                    imsCallStateToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_PHONE_STATE:
-                    phoneStateToJson(formatter);
-                    break;
-
-                case TelephonyEventLog.TAG_SMS:
-                    formatter.format(", \"%s\":\"%s\"", JSON_KEY_TAG, JSON_TAG_SMS);
-                    break;
-
-                default:
-                    formatter.format(", \"%s\":\"UNKNOWN(%d)\"", JSON_KEY_TAG, tag);
-                    break;
-            }
-            sb.append("},");
-            return sb.toString();
-        }
-
-        private void serviceStateToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\""
-                            + ",\"%s\":%d,\"%s\":%d,\"%s\":%d,\"%s\":%d"
-                            + ",\"%s\":\"%s\",\"%s\":\"%s\",\"%s\":\"%s\""
-                            + ",\"%s\":\"%s\",\"%s\":\"%s\",\"%s\":\"%s\""
-                            + ",\"%s\":%d,\"%s\":%d",
-                    JSON_KEY_TAG, JSON_TAG_SERVICE_STATE,
-                    JSON_KEY_REG_STATE, data.getInt("voiceRegState"),
-                    JSON_KEY_DATA_REG_STATE, data.getInt("dataRegState"),
-                    JSON_KEY_ROAMING_TYPE, data.getInt("voiceRoamingType"),
-                    JSON_KEY_DATA_ROAMING_TYPE, data.getInt("dataRoamingType"),
-                    JSON_KEY_OPERATOR_ALPHA_LONG, data.getString("operator-alpha-long"),
-                    JSON_KEY_OPERATOR_ALPHA_SHORT, data.getString("operator-alpha-short"),
-                    JSON_KEY_OPERATOR_NUMERIC, data.getString("operator-numeric"),
-                    JSON_KEY_DATA_OPERATOR_ALPHA_LONG, data.getString("data-operator-alpha-long"),
-                    JSON_KEY_DATA_OPERATOR_ALPHA_SHORT, data.getString("data-operator-alpha-short"),
-                    JSON_KEY_DATA_OPERATOR_NUMERIC, data.getString("data-operator-numeric"),
-                    JSON_KEY_RAT, data.getInt("radioTechnology"),
-                    JSON_KEY_DATA_RAT, data.getInt("dataRadioTechnology"));
-        }
-
-        private void imsConnectionStateToJson(Formatter formatter) {
-            if (data == null) {
-                formatter.format(", \"%s\":\"%s\", \"%s\":%d",
-                        JSON_KEY_TAG, JSON_TAG_IMS_CONNECTION_STATE, JSON_KEY_STATE, param1);
-            } else {
-                formatter.format(", \"%s\":\"%s\""
-                                + ", \"%s\":%d"
-                                + ", \"%s\":{\"%s\":%d,\"%s\":%d,\"%s\":%s}",
-                        JSON_KEY_TAG, JSON_TAG_IMS_CONNECTION_STATE,
-                        JSON_KEY_STATE, param1,
-                        JSON_KEY_REASON_INFO,
-                        JSON_KEY_REASON_INFO_CODE, data.getInt(
-                                TelephonyEventLog.DATA_KEY_REASONINFO_CODE),
-                        JSON_KEY_REASON_INFO_EXTRA_CODE, data.getInt(
-                                TelephonyEventLog.DATA_KEY_REASONINFO_EXTRA_CODE),
-                        JSON_KEY_REASON_INFO_EXTRA_MESSAGE, data.getString(
-                                TelephonyEventLog.DATA_KEY_REASONINFO_EXTRA_MESSAGE));
-            }
-        }
-
-        private void imsCapabilitiesToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\""
-                            + ",\"%s\":%b,\"%s\":%b,\"%s\":%b"
-                            + ",\"%s\":%b,\"%s\":%b,\"%s\":%b",
-                    JSON_KEY_TAG, JSON_TAG_IMS_CAPABILITIES,
-                    JSON_KEY_VOLTE, data.getBoolean(TelephonyEventLog.DATA_KEY_VOLTE),
-                    JSON_KEY_VILTE, data.getBoolean(TelephonyEventLog.DATA_KEY_VILTE),
-                    JSON_KEY_VOWIFI, data.getBoolean(TelephonyEventLog.DATA_KEY_VOWIFI),
-                    JSON_KEY_VIWIFI, data.getBoolean(TelephonyEventLog.DATA_KEY_VIWIFI),
-                    JSON_KEY_UTLTE, data.getBoolean(TelephonyEventLog.DATA_KEY_UTLTE),
-                    JSON_KEY_UTWIFI, data.getBoolean(TelephonyEventLog.DATA_KEY_UTWIFI));
-        }
-
-        private void dataCallListToJson(StringBuilder sb, Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\",\"%s\":[",
-                    JSON_KEY_TAG, JSON_TAG_DATA_CALL_LIST, JSON_KEY_DATA_CALLS);
-            int[] statuses = data.getIntArray(TelephonyEventLog.DATA_KEY_DATA_CALL_STATUSES);
-            int[] cids = data.getIntArray(TelephonyEventLog.DATA_KEY_DATA_CALL_CIDS);
-            int[] actives = data.getIntArray(TelephonyEventLog.DATA_KEY_DATA_CALL_ACTIVES);
-            String[] types = data.getStringArray(TelephonyEventLog.DATA_KEY_DATA_CALL_TYPES);
-            String[] ifnames = data.getStringArray(TelephonyEventLog.DATA_KEY_DATA_CALL_IFNAMES);
-            for (int i = 0; i < cids.length; i++) {
-                formatter.format("{\"%s\":%d,\"%s\":%d,\"%s\":%d"
-                                + ",\"%s\":\"%s\",\"%s\":\"%s\"},",
-                        JSON_KEY_STATUS, statuses[i], JSON_KEY_CID, cids[i],
-                        JSON_KEY_ACTIVE, actives[i],
-                        JSON_KEY_TYPE, types[i], JSON_KEY_IFNAME, ifnames[i]);
-            }
-            sb.append("]");
-        }
-
-        private void rilRequestToJson(Formatter formatter) {
-            switch (param1) {
-                case RIL_REQUEST_SETUP_DATA_CALL:
-                    formatter.format(", \"%s\":\"%s\""
-                                    + ",\"%s\":%d,\"%s\":\"%s\",\"%s\":\"%s\""
-                                    + ",\"%s\":\"%s\",\"%s\":\"%s\"",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_SETUP_DATA_CALL,
-                            JSON_KEY_SERIAL, param2,
-                            JSON_KEY_RAT, data.getString(
-                                    TelephonyEventLog.DATA_KEY_RAT),
-                            JSON_KEY_PROFILE, data.getString(
-                                    TelephonyEventLog.DATA_KEY_DATA_PROFILE),
-                            JSON_KEY_APN, data.getString(
-                                    TelephonyEventLog.DATA_KEY_APN),
-                            JSON_KEY_PROTOCOL, data.getString(
-                                    TelephonyEventLog.DATA_KEY_PROTOCOL));
-                    break;
-                case RIL_REQUEST_DEACTIVATE_DATA_CALL:
-                    formatter.format(", \"%s\":\"%s\""
-                                    + ",\"%s\":%d,\"%s\":%d,\"%s\":%d",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_DEACTIVATE_DATA_CALL,
-                            JSON_KEY_SERIAL, param2,
-                            JSON_KEY_CID, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_CID),
-                            JSON_KEY_REASON, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_DEACTIVATE_REASON));
-                    break;
-                case RIL_REQUEST_DIAL:
-                    formatter.format(", \"%s\":\"%s\""
-                                    + ",\"%s\":%d,\"%s\":%d",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_DIAL,
-                            JSON_KEY_SERIAL, param2,
-                            JSON_KEY_CLIR_MODE, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_CLIR_MODE));
-                    break;
-                case RIL_REQUEST_HANGUP:
-                case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
-                case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
-                    formatter.format(", \"%s\":\"%s\""
-                                    + ",\"%s\":%d, \"%s\":%d"
-                                    + ",\"%s\":\"%d\"",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_HANGUP,
-                            JSON_KEY_SERIAL, param2, JSON_KEY_EVT, param1,
-                            JSON_KEY_GSM_INDEX, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_RIL_HANGUP_GSM_INDEX));
-                    break;
-                case RIL_REQUEST_ANSWER:
-                    formatter.format(", \"%s\":\"%s\",\"%s\":%d",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_ANSWER, JSON_KEY_SERIAL, param2);
-                    break;
-
-                case RIL_REQUEST_SEND_SMS:
-                case RIL_REQUEST_SEND_SMS_EXPECT_MORE:
-                case RIL_REQUEST_CDMA_SEND_SMS:
-                case RIL_REQUEST_IMS_SEND_SMS:
-                    formatter.format(", \"%s\":\"%s\",\"%s\":%d",
-                            JSON_KEY_TAG, JSON_TAG_RIL_REQUEST_SEND_SMS, JSON_KEY_SERIAL, param2);
-                    break;
-            }
-        }
-
-        private void rilResponseToJson(Formatter formatter) {
-            switch (param1) {
-                case RIL_REQUEST_SETUP_DATA_CALL:
-                    formatter.format(", \"%s\":\"%s\""
-                                    + ",\"%s\":%d,\"%s\":%d,\"%s\":%d"
-                                    + ",\"%s\":%d,\"%s\":%d"
-                                    + ",\"%s\":\"%s\",\"%s\":\"%s\"",
-                            JSON_KEY_TAG, JSON_TAG_RIL_RESPONSE_SETUP_DATA_CALL,
-                            JSON_KEY_SERIAL, param2,
-                            JSON_KEY_STATUS, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_STATUS),
-                            JSON_KEY_RETRY, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_RETRY),
-                            JSON_KEY_CID, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_CID),
-                            JSON_KEY_ACTIVE, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_ACTIVE),
-                            JSON_KEY_TYPE, data.getString(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_TYPE),
-                            JSON_KEY_IFNAME, data.getString(
-                                    TelephonyEventLog.DATA_KEY_DATA_CALL_IFNAME));
-                    break;
-
-                case RIL_REQUEST_DEACTIVATE_DATA_CALL:
-                case RIL_REQUEST_HANGUP:
-                case RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND:
-                case RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND:
-                case RIL_REQUEST_DIAL:
-                case RIL_REQUEST_ANSWER:
-                    formatter.format(", \"%s\":\"%s\",\"%s\":%d",
-                            JSON_KEY_TAG, rilResponseToString(param1), JSON_KEY_SERIAL, param2);
-                    break;
-
-                case RIL_REQUEST_SEND_SMS:
-                case RIL_REQUEST_SEND_SMS_EXPECT_MORE:
-                case RIL_REQUEST_CDMA_SEND_SMS:
-                case RIL_REQUEST_IMS_SEND_SMS:
-                    formatter.format(", \"%s\":\"%s\",\"%s\":%d"
-                                    + ",\"%s\":%d,\"%s\":%d",
-                            JSON_KEY_TAG, rilResponseToString(param1), JSON_KEY_SERIAL, param2,
-                            JSON_KEY_SMS_MESSAGE_REF, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_SMS_MESSAGE_REF),
-                            JSON_KEY_SMS_ERROR_CODE, data.getInt(
-                                    TelephonyEventLog.DATA_KEY_SMS_ERROR_CODE));
-                    break;
-            }
-            formatter.format(", \"%s\":%d",
-                    JSON_KEY_RIL_ERROR, data.getInt(TelephonyEventLog.DATA_KEY_RIL_ERROR));
-        }
-
-        private void unsolRilResponseToJson(Formatter formatter) {
-            switch (param1) {
-                case RIL_UNSOL_CALL_RING:
-                    formatter.format(", \"%s\":\"%s\"", JSON_KEY_TAG, JSON_TAG_RIL_UNSOL_CALL_RING);
-                    break;
-                case RIL_UNSOL_SRVCC_STATE_NOTIFY:
-                    formatter.format(", \"%s\":\"%s\",\"%s\":%d",
-                            JSON_KEY_TAG, JSON_TAG_RIL_UNSOL_SRVCC_STATE_NOTIFY,
-                            JSON_KEY_STATE, param2);
-                    break;
-                case RIL_UNSOL_RESPONSE_NEW_SMS:
-                    formatter.format(", \"%s\":\"%s\"",
-                            JSON_KEY_TAG, JSON_TAG_RIL_UNSOL_RESPONSE_NEW_SMS);
-                    break;
-                case RIL_UNSOL_RESPONSE_CDMA_NEW_SMS:
-                    formatter.format(", \"%s\":\"%s\"",
-                            JSON_KEY_TAG, JSON_TAG_RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
-                    break;
-            }
-        }
-
-        private void imsCallEventToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\", \"%s\":\"%s\",\"%s\":%d",
-                    JSON_KEY_TAG, JSON_TAG_IMS_CALL, JSON_KEY_EVT, imsCallEventToString(tag),
-                    JSON_KEY_CALL_ID, param1);
-        }
-
-        private void imsHandoverToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\", \"%s\":\"%s\",\"%s\":%d"
-                            + ",\"%s\":%d,\"%s\":%d"
-                            + ",\"%s\":%d,\"%s\":%d,\"%s\":\"%s\"",
-                    JSON_KEY_TAG, JSON_TAG_IMS_CALL_HANDOVER,
-                    JSON_KEY_EVT, imsCallEventToString(tag), JSON_KEY_CALL_ID, param1,
-                    JSON_KEY_SRC_TECH, data.getInt(TelephonyEventLog.DATA_KEY_SRC_TECH),
-                    JSON_KEY_TARGET_TECH, data.getInt(TelephonyEventLog.DATA_KEY_TARGET_TECH),
-                    JSON_KEY_REASON_INFO_CODE, data.getInt(
-                            TelephonyEventLog.DATA_KEY_REASONINFO_CODE),
-                    JSON_KEY_REASON_INFO_EXTRA_CODE, data.getInt(
-                            TelephonyEventLog.DATA_KEY_REASONINFO_EXTRA_CODE),
-                    JSON_KEY_REASON_INFO_EXTRA_MESSAGE, data.getString(
-                            TelephonyEventLog.DATA_KEY_REASONINFO_EXTRA_MESSAGE));
-        }
-
-        private void imsCallStateToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\", \"%s\":%d, \"%s\":%d",
-                    JSON_KEY_TAG, JSON_TAG_IMS_CALL_STATE, JSON_KEY_CALL_ID, param1,
-                    JSON_KEY_STATE, param2);
-        }
-
-        private void phoneStateToJson(Formatter formatter) {
-            formatter.format(", \"%s\":\"%s\", \"%s\":%d",
-                    JSON_KEY_TAG, JSON_TAG_PHONE_STATE, JSON_KEY_STATE, param1);
-        }
-    }
-    private final List<Event> mEvents = new ArrayList<Event>();
+    private final int MAX_NUMBER_OF_EVENTS = 100;
+    private final int MIN_TIME_OFFSET = 900000; // 15 minutes
+    private final List<TelephonyEvent> mEvents = new ArrayList<TelephonyEvent>();
+    private long mLastSentEventTimeMillis = System.currentTimeMillis();
 
     /**
      * Implementation of the ITelephonyDebug interface.
      */
     private final ITelephonyDebug.Stub mBinder = new ITelephonyDebug.Stub() {
+
+        private final List<ITelephonyDebugSubscriber> mSubscribers = new ArrayList<>();
+
         public void writeEvent(long timestamp, int phoneId, int tag,
                 int param1, int param2, Bundle data) {
+            final TelephonyEvent ev = new TelephonyEvent(timestamp, phoneId, tag,
+                    param1, param2, data);
+            TelephonyEvent[] events = null;
+
             if (VDBG) {
-                Log.v(TAG, String.format("writeEvent(%d, %d, %d, %d, %d)",
-                        timestamp, phoneId, tag, param1, param2));
+                Log.v(TAG, "writeEvent(" + ev.toString() + ")");
             }
+
             synchronized (mEvents) {
-                mEvents.add(new Event(timestamp, phoneId, tag, param1, param2, data));
+                mEvents.add(ev);
+
+                final long currentTimeMillis = System.currentTimeMillis();
+                final long timeOffset = currentTimeMillis - mLastSentEventTimeMillis;
+                if (timeOffset > MIN_TIME_OFFSET
+                        || timeOffset < 0 // system time has changed
+                        || mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
+                    // batch events
+                    mLastSentEventTimeMillis = currentTimeMillis;
+                    events = new TelephonyEvent[mEvents.size()];
+                    mEvents.toArray(events);
+                    mEvents.clear();
+                }
+            }
+
+            if (events != null) {
+                synchronized (mSubscribers) {
+                    for (ITelephonyDebugSubscriber s : mSubscribers) {
+                        try {
+                            s.onEvents(events);
+                        } catch (RemoteException ex) {
+                            Log.e(TAG, "RemoteException " + ex);
+                        }
+                    }
+                }
+            }
+        }
+
+        public void subscribe(ITelephonyDebugSubscriber subscriber) {
+            if (VDBG) Log.v(TAG, "subscribe");
+            synchronized (mSubscribers) {
+                mSubscribers.add(subscriber);
+            }
+
+            synchronized (mEvents) {
+                try {
+                    // send cached events
+                    TelephonyEvent[] events = new TelephonyEvent[mEvents.size()];
+                    mEvents.toArray(events);
+                    subscriber.onEvents(events);
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "RemoteException " + ex);
+                }
+            }
+        }
+
+        public void unsubscribe(ITelephonyDebugSubscriber subscriber) {
+            if (VDBG) Log.v(TAG, "unsubscribe");
+            synchronized (mSubscribers) {
+                mSubscribers.remove(subscriber);
             }
         }
     };
diff --git a/src/com/android/phone/VoicemailUtils.java b/src/com/android/phone/VoicemailUtils.java
new file mode 100644
index 0000000..f67c64b
--- /dev/null
+++ b/src/com/android/phone/VoicemailUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 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.phone;
+
+import android.content.Context;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
+import android.telecom.PhoneAccountHandle;
+
+public class VoicemailUtils {
+
+    public static void setConfigurationState(Context context, PhoneAccountHandle accountHandle,
+            int configurationState) {
+        VoicemailContract.Status.setStatus(context, accountHandle,
+                configurationState,
+                Status.DATA_CHANNEL_STATE_IGNORE,
+                Status.NOTIFICATION_CHANNEL_STATE_IGNORE);
+    }
+
+    public static void setDataChannelState(Context context, PhoneAccountHandle accountHandle,
+            int dataChannelState) {
+        VoicemailContract.Status.setStatus(context, accountHandle,
+                Status.CONFIGURATION_STATE_IGNORE,
+                dataChannelState,
+                Status.NOTIFICATION_CHANNEL_STATE_IGNORE);
+    }
+
+    public static void setNotificationChannelState(Context context,
+            PhoneAccountHandle accountHandle, int notificationChannelState) {
+        VoicemailContract.Status.setStatus(context, accountHandle,
+                Status.CONFIGURATION_STATE_IGNORE,
+                Status.DATA_CHANNEL_STATE_IGNORE,
+                notificationChannelState);
+    }
+}
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index 172d1a9..f452bab 100644
--- a/src/com/android/phone/common/mail/MailTransport.java
+++ b/src/com/android/phone/common/mail/MailTransport.java
@@ -17,10 +17,21 @@
 
 import android.content.Context;
 import android.net.Network;
+import android.provider.VoicemailContract.Status;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.common.mail.store.ImapStore;
 import com.android.phone.common.mail.utils.LogUtils;
+import com.android.phone.vvm.omtp.imap.ImapHelper;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
 import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.List;
@@ -32,15 +43,6 @@
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-
 /**
  * Make connection and perform operations on mail server by reading and writing lines.
  */
@@ -54,17 +56,21 @@
     private static final HostnameVerifier HOSTNAME_VERIFIER =
             HttpsURLConnection.getDefaultHostnameVerifier();
 
-    private Context mContext;
-    private Network mNetwork;
-    private String mHost;
-    private int mPort;
+    private final Context mContext;
+    private final ImapHelper mImapHelper;
+    private final Network mNetwork;
+    private final String mHost;
+    private final int mPort;
     private Socket mSocket;
     private BufferedInputStream mIn;
     private BufferedOutputStream mOut;
-    private int mFlags;
+    private final int mFlags;
+    private SocketCreator mSocketCreator;
 
-    public MailTransport(Context context, Network network, String address, int port, int flags) {
+    public MailTransport(Context context, ImapHelper imapHelper, Network network, String address,
+            int port, int flags) {
         mContext = context;
+        mImapHelper = imapHelper;
         mNetwork = network;
         mHost = address;
         mPort = port;
@@ -77,7 +83,7 @@
      */
     @Override
     public MailTransport clone() {
-        return new MailTransport(mContext, mNetwork, mHost, mPort, mFlags);
+        return new MailTransport(mContext, mImapHelper, mNetwork, mHost, mPort, mFlags);
     }
 
     public boolean canTrySslSecurity() {
@@ -92,55 +98,112 @@
      * Attempts to open a connection using the Uri supplied for connection parameters.  Will attempt
      * an SSL connection if indicated.
      */
-    public void open() throws MessagingException, CertificateValidationException {
+    public void open() throws MessagingException {
         LogUtils.d(TAG, "*** IMAP open " + mHost + ":" + String.valueOf(mPort));
 
-        List<SocketAddress> socketAddresses = new ArrayList<SocketAddress>();
-        try {
-            if (canTrySslSecurity()) {
-                mSocket = HttpsURLConnection.getDefaultSSLSocketFactory().createSocket();
-                socketAddresses.add(new InetSocketAddress(mHost, mPort));
-            } else {
-                if (mNetwork == null) {
-                    mSocket = new Socket();
-                    socketAddresses.add(new InetSocketAddress(mHost, mPort));
-                } else {
-                    InetAddress[] inetAddresses = mNetwork.getAllByName(mHost);
-                    for (int i = 0; i < inetAddresses.length; i++) {
-                        socketAddresses.add(new InetSocketAddress(inetAddresses[i], mPort));
-                    }
-                    mSocket = mNetwork.getSocketFactory().createSocket();
+        List<InetSocketAddress> socketAddresses = new ArrayList<InetSocketAddress>();
+
+        if (mNetwork == null) {
+            socketAddresses.add(new InetSocketAddress(mHost, mPort));
+        } else {
+            try {
+                InetAddress[] inetAddresses = mNetwork.getAllByName(mHost);
+                if (inetAddresses.length == 0) {
+                    throw new MessagingException(MessagingException.IOERROR,
+                            "Host name " + mHost + "cannot be resolved on designated network");
                 }
+                for (int i = 0; i < inetAddresses.length; i++) {
+                    socketAddresses.add(new InetSocketAddress(inetAddresses[i], mPort));
+                }
+            } catch (IOException ioe) {
+                LogUtils.d(TAG, ioe.toString());
+                mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR);
+                throw new MessagingException(MessagingException.IOERROR, ioe.toString());
             }
-        } catch (IOException ioe) {
-            LogUtils.d(TAG, ioe.toString());
-            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
         }
 
+        boolean success = false;
         while (socketAddresses.size() > 0) {
+            mSocket = createSocket();
             try {
-                mSocket.connect(socketAddresses.remove(0), SOCKET_CONNECT_TIMEOUT);
+                InetSocketAddress address = socketAddresses.remove(0);
+                mSocket.connect(address, SOCKET_CONNECT_TIMEOUT);
 
-                // After the socket connects to an SSL server, confirm that the hostname is as
-                // expected
-                if (canTrySslSecurity() && !canTrustAllCertificates()) {
-                    verifyHostname(mSocket, mHost);
+                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.
+                     */
+                    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);
+                    }
                 }
 
                 mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
                 mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
                 mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
+                success = true;
                 return;
             } catch (IOException ioe) {
                 LogUtils.d(TAG, ioe.toString());
                 if (socketAddresses.size() == 0) {
                     // Only throw an error when there are no more sockets to try.
+                    mImapHelper
+                            .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR);
                     throw new MessagingException(MessagingException.IOERROR, ioe.toString());
                 }
+            } finally {
+                if (!success) {
+                    try {
+                        mSocket.close();
+                        mSocket = null;
+                    } catch (IOException ioe) {
+                        throw new MessagingException(MessagingException.IOERROR, ioe.toString());
+                    }
+
+                }
             }
         }
     }
 
+    // For testing. We need something that can replace the behavior of "new Socket()"
+    @VisibleForTesting
+    interface SocketCreator {
+
+        Socket createSocket() throws MessagingException;
+    }
+
+    @VisibleForTesting
+    void setSocketCreator(SocketCreator creator) {
+        mSocketCreator = creator;
+    }
+
+    protected Socket createSocket() throws MessagingException {
+        if (mSocketCreator != null) {
+            return mSocketCreator.createSocket();
+        }
+
+        if (mNetwork == null) {
+            LogUtils.v(TAG, "createSocket: network not specified");
+            return new Socket();
+        }
+
+        try {
+            LogUtils.v(TAG, "createSocket: network specified");
+            return mNetwork.getSocketFactory().createSocket();
+        } 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.
@@ -158,7 +221,7 @@
      * @throws IOException if something goes wrong handshaking with the server
      * @throws SSLPeerUnverifiedException if the server cannot prove its identity
       */
-    private static void verifyHostname(Socket socket, String hostname) throws IOException {
+    private void verifyHostname(Socket socket, String hostname) throws IOException {
         // The code at the start of OpenSSLSocketImpl.startHandshake()
         // ensures that the call is idempotent, so we can safely call it.
         SSLSocket ssl = (SSLSocket) socket;
@@ -166,6 +229,7 @@
 
         SSLSession session = ssl.getSession();
         if (session == null) {
+            mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
             throw new SSLException("Cannot verify SSL socket without session");
         }
         // TODO: Instead of reporting the name of the server we think we're connecting to,
@@ -173,8 +237,9 @@
         // in the verifier code and is not available in the verifier API, and extracting the
         // CN & alts is beyond the scope of this patch.
         if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
-            throw new SSLPeerUnverifiedException(
-                    "Certificate hostname not useable for server: " + hostname);
+            mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
+            throw new SSLPeerUnverifiedException("Certificate hostname not useable for server: "
+                    + session.getPeerPrincipal());
         }
     }
 
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index ace7029..9207aa9 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -15,6 +15,7 @@
  */
 package com.android.phone.common.mail.store;
 
+import android.provider.VoicemailContract.Status;
 import android.text.TextUtils;
 
 import com.android.phone.common.mail.AuthenticationFailedException;
@@ -105,9 +106,12 @@
             doLogin();
         } catch (SSLException e) {
             LogUtils.d(TAG, "SSLException ", e);
+            mImapStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
             throw new CertificateValidationException(e.getMessage(), e);
         } catch (IOException ioe) {
             LogUtils.d(TAG, "IOException", ioe);
+            mImapStore.getImapHelper()
+                    .setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
             throw ioe;
         } finally {
             destroyResponses();
@@ -144,6 +148,8 @@
             if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
                     ImapConstants.EXPIRED.equals(code) ||
                     (ImapConstants.NO.equals(status) && TextUtils.isEmpty(code))) {
+                mImapStore.getImapHelper()
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION);
                 throw new AuthenticationFailedException(alertText, ie);
             }
 
@@ -233,12 +239,12 @@
             final String alert = response.getAlertTextOrEmpty().getString();
             final String responseCode = response.getResponseCodeOrEmpty().getString();
             destroyResponses();
-
+            mImapStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
             // if the response code indicates an error occurred within the server, indicate that
             if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
+
                 throw new MessagingException(MessagingException.SERVER_ERROR, alert);
             }
-
             throw new ImapException(toString, status, alert, responseCode);
         }
         return responses;
diff --git a/src/com/android/phone/common/mail/store/ImapFolder.java b/src/com/android/phone/common/mail/store/ImapFolder.java
index 1d17ea9..90c552d 100644
--- a/src/com/android/phone/common/mail/store/ImapFolder.java
+++ b/src/com/android/phone/common/mail/store/ImapFolder.java
@@ -19,6 +19,8 @@
 import android.content.SharedPreferences;
 import android.preference.PreferenceManager;
 import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
+import android.telecom.Voicemail;
 import android.text.TextUtils;
 import android.util.Base64DataException;
 import android.util.Log;
@@ -727,6 +729,7 @@
                     mMode = MODE_READ_WRITE;
                 }
             } else if (response.isTagged()) { // Not OK
+                mStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
                 throw new MessagingException("Can't open mailbox: "
                         + response.getStatusResponseTextOrEmpty());
             }
@@ -789,6 +792,7 @@
             mConnection = null; // To prevent close() from returning the connection to the pool.
             close(false);
         }
+        mStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
         return new MessagingException(MessagingException.IOERROR, "IO Error", ioe);
     }
 
diff --git a/src/com/android/phone/common/mail/store/ImapStore.java b/src/com/android/phone/common/mail/store/ImapStore.java
index 63a5c36..c8095e5 100644
--- a/src/com/android/phone/common/mail/store/ImapStore.java
+++ b/src/com/android/phone/common/mail/store/ImapStore.java
@@ -23,6 +23,7 @@
 import com.android.phone.common.mail.Message;
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.common.mail.internet.MimeMessage;
+import com.android.phone.vvm.omtp.imap.ImapHelper;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -33,10 +34,11 @@
      * should be returned on FetchProfile.Item.BODY_SANE requests. We'll use 125k now.
      */
     public static final int FETCH_BODY_SANE_SUGGESTED_SIZE = (125 * 1024);
-    private Context mContext;
-    private String mUsername;
-    private String mPassword;
-    private MailTransport mTransport;
+    private final Context mContext;
+    private final ImapHelper mHelper;
+    private final String mUsername;
+    private final String mPassword;
+    private final MailTransport mTransport;
     private ImapConnection mConnection;
 
     public static final int FLAG_NONE         = 0x00;    // No flags
@@ -49,18 +51,24 @@
     /**
      * Contains all the information necessary to log into an imap server
      */
-    public ImapStore(Context context, String username, String password, int port,
+    public ImapStore(Context context, ImapHelper helper, String username, String password, int port,
             String serverName, int flags, Network network) {
         mContext = context;
+        mHelper = helper;
         mUsername = username;
         mPassword = password;
-        mTransport = new MailTransport(context, network, serverName, port, flags);
+        mTransport = new MailTransport(context, this.getImapHelper(),
+                network, serverName, port, flags);
     }
 
     public Context getContext() {
         return mContext;
     }
 
+    public ImapHelper getImapHelper() {
+        return mHelper;
+    }
+
     public String getUsername() {
         return mUsername;
     }
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 9393f81..57e23a9 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -96,6 +96,14 @@
                 .getBoolean(CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOLEAN);
     }
 
+    public boolean isPrefetchEnabled() {
+        if (mCarrierConfig == null) {
+            return false;
+        }
+        return mCarrierConfig
+                .getBoolean(CarrierConfigManager.KEY_VVM_PREFETCH_BOOLEAN);
+    }
+
     public void startActivation() {
         OmtpMessageSender messageSender = getMessageSender();
         if (messageSender != null) {
diff --git a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
index eb6a175..3862d54 100644
--- a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
+++ b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
@@ -39,7 +39,7 @@
     private ContentResolver mContentResolver;
     private Uri mUri;
 
-    VoicemailFetchedCallback(Context context, Uri uri) {
+    public VoicemailFetchedCallback(Context context, Uri uri) {
         mContentResolver = context.getContentResolver();
         mUri = uri;
     }
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index fd56c77..da2d34b 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -20,6 +20,7 @@
 import android.net.Network;
 import android.preference.PreferenceManager;
 import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.telephony.TelephonyManager;
@@ -27,6 +28,7 @@
 import android.util.Log;
 
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailUtils;
 import com.android.phone.common.mail.Address;
 import com.android.phone.common.mail.Body;
 import com.android.phone.common.mail.BodyPart;
@@ -100,8 +102,10 @@
             }
 
             mImapStore = new ImapStore(
-                    context, username, password, port, serverName, auth, network);
+                    context, this, username, password, port, serverName, auth, network);
         } catch (NumberFormatException e) {
+            VoicemailUtils.setDataChannelState(
+                    mContext, mPhoneAccount, Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION);
             LogUtils.w(TAG, "Could not parse port number");
         }
 
@@ -133,6 +137,10 @@
         return setFlags(voicemails, Flag.DELETED);
     }
 
+    public void setDataChannelState(int dataChannelState) {
+        VoicemailUtils.setDataChannelState(mContext, mPhoneAccount, dataChannelState);
+    }
+
     /**
      * Set flags on the server for a given set of voicemails.
      *
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java
new file mode 100644
index 0000000..f0d21d1
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.phone.vvm.omtp.sync;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.VoicemailContract;
+import android.util.Log;
+
+public class OmtpVvmSyncReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "OmtpVvmSyncReceiver";
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        if (VoicemailContract.ACTION_SYNC_VOICEMAIL.equals(intent.getAction())) {
+            Log.v(TAG, "Sync intent received");
+            Intent syncIntent = OmtpVvmSyncService
+                    .getSyncIntent(context, OmtpVvmSyncService.SYNC_FULL_SYNC, null, true);
+            context.startService(syncIntent);
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index b3659ae..36deb08 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -22,14 +22,20 @@
 import android.content.Intent;
 import android.net.Network;
 import android.net.NetworkInfo;
+import android.net.Uri;
 import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailUtils;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.LocalLogHelper;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 
 import java.util.HashMap;
@@ -207,9 +213,9 @@
 
                 boolean success = true;
                 if (voicemail == null) {
-                    success = syncAll(action, imapHelper);
+                    success = syncAll(action, imapHelper, phoneAccount);
                 } else {
-                    success = syncOne(imapHelper, voicemail);
+                    success = syncOne(imapHelper, voicemail, phoneAccount);
                 }
                 imapHelper.updateQuota();
 
@@ -223,6 +229,8 @@
                     // Nothing more to do here, just exit.
                     VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
                             phoneAccount);
+                    VoicemailUtils.setDataChannelState(
+                            this, phoneAccount, Status.DATA_CHANNEL_STATE_OK);
                     return;
                 }
             }
@@ -233,7 +241,7 @@
         }
     }
 
-    private boolean syncAll(String action, ImapHelper imapHelper) {
+    private boolean syncAll(String action, ImapHelper imapHelper, PhoneAccountHandle account) {
         boolean uploadSuccess = true;
         boolean downloadSuccess = true;
 
@@ -241,7 +249,7 @@
             uploadSuccess = upload(imapHelper);
         }
         if (SYNC_FULL_SYNC.equals(action) || SYNC_DOWNLOAD_ONLY.equals(action)) {
-            downloadSuccess = download(imapHelper);
+            downloadSuccess = download(imapHelper, account);
         }
 
         Log.v(TAG, "upload succeeded: [" + String.valueOf(uploadSuccess)
@@ -259,7 +267,17 @@
         return success;
     }
 
-    private boolean syncOne(ImapHelper imapHelper, Voicemail voicemail) {
+    private boolean syncOne(ImapHelper imapHelper, Voicemail voicemail,
+            PhoneAccountHandle account) {
+        OmtpVvmCarrierConfigHelper carrierConfigHelper =
+                new OmtpVvmCarrierConfigHelper(
+                        this, PhoneUtils.getSubIdForPhoneAccountHandle(account));
+        if (carrierConfigHelper.isPrefetchEnabled()) {
+            VoicemailFetchedCallback callback = new VoicemailFetchedCallback(this,
+                    voicemail.getUri());
+            imapHelper.fetchVoicemailPayload(callback, voicemail.getSourceData());
+        }
+
         return imapHelper.fetchTranscription(
                 new TranscriptionFetchedCallback(this, voicemail),
                 voicemail.getSourceData());
@@ -314,7 +332,7 @@
         return success;
     }
 
-    private boolean download(ImapHelper imapHelper) {
+    private boolean download(ImapHelper imapHelper, PhoneAccountHandle account) {
         List<Voicemail> serverVoicemails = imapHelper.fetchAllVoicemails();
         List<Voicemail> localVoicemails = mQueryHelper.getAllVoicemails();
 
@@ -349,8 +367,16 @@
         }
 
         // The leftover messages are messages that exist on the server but not locally.
+        OmtpVvmCarrierConfigHelper carrierConfigHelper =
+                new OmtpVvmCarrierConfigHelper(
+                        this, PhoneUtils.getSubIdForPhoneAccountHandle(account));
+        boolean prefetchEnabled = carrierConfigHelper.isPrefetchEnabled();
         for (Voicemail remoteVoicemail : remoteMap.values()) {
-            VoicemailContract.Voicemails.insert(this, remoteVoicemail);
+            Uri uri = VoicemailContract.Voicemails.insert(this, remoteVoicemail);
+            if (prefetchEnabled) {
+                VoicemailFetchedCallback fetchedCallback = new VoicemailFetchedCallback(this, uri);
+                imapHelper.fetchVoicemailPayload(fetchedCallback, remoteVoicemail.getSourceData());
+            }
         }
 
         return true;
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
index 8bef9dc..884bec9 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
@@ -23,10 +23,13 @@
 import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.util.Log;
 
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailUtils;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 
 /**
@@ -47,32 +50,32 @@
     protected PhoneAccountHandle mPhoneAccount;
     protected NetworkRequest mNetworkRequest;
     private ConnectivityManager mConnectivityManager;
-
+    private final OmtpVvmCarrierConfigHelper mCarrierConfigHelper;
+    private final int mSubId;
     private boolean mRequestSent = false;
     private boolean mResultReceived = false;
 
     public VvmNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount) {
         mContext = context;
         mPhoneAccount = phoneAccount;
-        mNetworkRequest = getNetworkRequest(context, phoneAccount);
+        mSubId = PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount);
+        mCarrierConfigHelper = new OmtpVvmCarrierConfigHelper(context, mSubId);
+        mNetworkRequest = createNetworkRequest();
     }
 
     /**
      * @return NetworkRequest for a proper transport type. Use only cellular network if the carrier
      * requires it. Otherwise use whatever available.
      */
-    private NetworkRequest getNetworkRequest(Context context, PhoneAccountHandle phoneAccount) {
-        int subId = PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount);
-        OmtpVvmCarrierConfigHelper carrierConfigHelper =
-                new OmtpVvmCarrierConfigHelper(context, subId);
+    private NetworkRequest createNetworkRequest() {
 
         NetworkRequest.Builder builder = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
-        if (carrierConfigHelper.isCellularDataRequired()) {
+        if (mCarrierConfigHelper.isCellularDataRequired()) {
             Log.d(TAG, "Transport type: CELLULAR");
             builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                    .setNetworkSpecifier(Integer.toString(subId));
+                    .setNetworkSpecifier(Integer.toString(mSubId));
         } else {
             Log.d(TAG, "Transport type: ANY");
         }
@@ -143,7 +146,14 @@
     @CallSuper
     public void onFailed(String reason) {
         Log.d(TAG, "onFailed: " + reason);
-        // TODO: Notify the user sync has failed?
+        if (mCarrierConfigHelper.isCellularDataRequired()) {
+            VoicemailUtils.setDataChannelState(
+                    mContext, mPhoneAccount,
+                    Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED);
+        } else {
+            VoicemailUtils.setDataChannelState(
+                    mContext, mPhoneAccount, Status.DATA_CHANNEL_STATE_NO_CONNECTION);
+        }
         releaseNetwork();
     }
 }
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index d1f4cc6..c039106 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -171,8 +171,8 @@
 
         @Override
         public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
-            Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
-                    connectionCapabilities);
+            Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s," +
+                    " connectionCapabilities: %s", c, connectionCapabilities);
             int capabilites = ImsConference.this.getConnectionCapabilities();
             setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities));
         }
@@ -269,37 +269,26 @@
      * @return The merged capabilities to be applied to the conference.
      */
     private int applyHostCapabilities(int conferenceCapabilities, int capabilities) {
-        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
-            conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
-        } else {
-            conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
-        }
+        conferenceCapabilities = changeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
+                    can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
 
-        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
-            conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
-        } else {
-            conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
-        }
+        conferenceCapabilities = changeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
+                    can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
 
-        if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
-            conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
-        } else {
-            conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
-        }
+        conferenceCapabilities = changeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
+                    can(capabilities, Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO));
 
-        if (can(capabilities, Connection.CAPABILITY_HIGH_DEF_AUDIO)) {
-            conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_HIGH_DEF_AUDIO);
-        } else {
-            conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_HIGH_DEF_AUDIO);
-        }
+        conferenceCapabilities = changeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
+                    can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO));
+
+        conferenceCapabilities = changeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_HIGH_DEF_AUDIO,
+                    can(capabilities, Connection.CAPABILITY_HIGH_DEF_AUDIO));
+
         return conferenceCapabilities;
     }
 
@@ -452,14 +441,20 @@
         // No-op
     }
 
-    private int applyCapability(int capabilities, int capability) {
-        int newCapabilities = capabilities | capability;
-        return newCapabilities;
-    }
-
-    private int removeCapability(int capabilities, int capability) {
-        int newCapabilities = capabilities & ~capability;
-        return newCapabilities;
+    /**
+     * Changes a capabilities bit-mask to add or remove a capability.
+     *
+     * @param capabilities The capabilities bit-mask.
+     * @param capability The capability to change.
+     * @param enabled Whether the capability should be set or removed.
+     * @return The capabilities bit-mask with the capability changed.
+     */
+    private int changeCapability(int capabilities, int capability, boolean enabled) {
+        if (enabled) {
+            return capabilities | capability;
+        } else {
+            return capabilities & ~capability;
+        }
     }
 
     /**
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index e8a93c6..fc4bb4d 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -102,7 +102,7 @@
             int subId = mPhone.getSubId();
             int color = PhoneAccount.NO_HIGHLIGHT_COLOR;
             int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-            String line1Number = mTelephonyManager.getLine1NumberForSubscriber(subId);
+            String line1Number = mTelephonyManager.getLine1Number(subId);
             if (line1Number == null) {
                 line1Number = "";
             }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 339db6d..0142d64 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -34,6 +34,7 @@
 import com.android.ims.ImsCallProfile;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection.Capability;
 import com.android.internal.telephony.Connection.PostDialListener;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
@@ -71,12 +72,11 @@
     private static final Map<String, String> sExtrasMap = createExtrasMap();
 
     private static final int MSG_SET_VIDEO_STATE = 8;
-    private static final int MSG_SET_LOCAL_VIDEO_CAPABILITY = 9;
-    private static final int MSG_SET_REMOTE_VIDEO_CAPABILITY = 10;
-    private static final int MSG_SET_VIDEO_PROVIDER = 11;
-    private static final int MSG_SET_AUDIO_QUALITY = 12;
-    private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 13;
-    private static final int MSG_CONNECTION_EXTRAS_CHANGED = 14;
+    private static final int MSG_SET_VIDEO_PROVIDER = 9;
+    private static final int MSG_SET_AUDIO_QUALITY = 10;
+    private static final int MSG_SET_CONFERENCE_PARTICIPANTS = 11;
+    private static final int MSG_CONNECTION_EXTRAS_CHANGED = 12;
+    private static final int MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES = 13;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -157,18 +157,6 @@
                     setVideoState(videoState);
                     break;
 
-                case MSG_SET_LOCAL_VIDEO_CAPABILITY:
-                    boolean localVideoCapable = false;
-                    localVideoCapable = (boolean) msg.obj;
-                    setLocalVideoCapable(localVideoCapable);
-                    break;
-
-                case MSG_SET_REMOTE_VIDEO_CAPABILITY:
-                    boolean remoteVideoCapable = false;
-                    remoteVideoCapable = (boolean) msg.obj;
-                    setRemoteVideoCapable(remoteVideoCapable);
-                    break;
-
                 case MSG_SET_VIDEO_PROVIDER:
                     VideoProvider videoProvider = (VideoProvider) msg.obj;
                     setVideoProvider(videoProvider);
@@ -188,6 +176,10 @@
                     final Bundle extras = (Bundle) msg.obj;
                     updateExtras(extras);
                     break;
+
+                case MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES:
+                    setOriginalConnectionCapabilities(msg.arg1);
+                    break;
             }
         }
     };
@@ -229,26 +221,15 @@
             mHandler.obtainMessage(MSG_SET_VIDEO_STATE, videoState).sendToTarget();
         }
 
-        /**
-         * The {@link com.android.internal.telephony.Connection} has reported a change in local
-         * video capability.
-         *
-         * @param capable True if capable.
+        /*
+         * The {@link com.android.internal.telephony.Connection} has reported a change in
+         * connection capability.
+         * @param capabilities bit mask containing voice or video or both capabilities.
          */
         @Override
-        public void onLocalVideoCapabilityChanged(boolean capable) {
-            mHandler.obtainMessage(MSG_SET_LOCAL_VIDEO_CAPABILITY, capable).sendToTarget();
-        }
-
-        /**
-         * The {@link com.android.internal.telephony.Connection} has reported a change in remote
-         * video capability.
-         *
-         * @param capable True if capable.
-         */
-        @Override
-        public void onRemoteVideoCapabilityChanged(boolean capable) {
-            mHandler.obtainMessage(MSG_SET_REMOTE_VIDEO_CAPABILITY, capable).sendToTarget();
+        public void onConnectionCapabilitiesChanged(int capabilities) {
+            mHandler.obtainMessage(MSG_SET_ORIGNAL_CONNECTION_CAPABILITIES,
+                    capabilities, 0).sendToTarget();
         }
 
         /**
@@ -343,23 +324,10 @@
     private boolean mIsMultiParty = false;
 
     /**
-     * Determines if the {@link TelephonyConnection} has local video capabilities.
-     * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
-     * ensuring the appropriate capabilities are set.  Since capabilities
-     * can be rebuilt at any time it is necessary to track the video capabilities between rebuild.
-     * The capabilities (including video capabilities) are communicated to the telecom
-     * layer.
+     * The {@link com.android.internal.telephony.Connection} capabilities associated with the
+     * current {@link #mOriginalConnection}.
      */
-    private boolean mLocalVideoCapable;
-
-    /**
-     * Determines if the {@link TelephonyConnection} has remote video capabilities.
-     * This is used when {@link TelephonyConnection#updateConnectionCapabilities()}} is called,
-     * ensuring the appropriate capabilities are set.  Since capabilities can be rebuilt at any time
-     * it is necessary to track the video capabilities between rebuild. The capabilities (including
-     * video capabilities) are communicated to the telecom layer.
-     */
-    private boolean mRemoteVideoCapable;
+    private int mOriginalConnectionCapabilities;
 
     /**
      * Determines if the {@link TelephonyConnection} is using wifi.
@@ -594,7 +562,7 @@
     }
 
     /**
-     * Builds call capabilities common to all TelephonyConnections. Namely, apply IMS-based
+     * Builds connection capabilities common to all TelephonyConnections. Namely, apply IMS-based
      * capabilities.
      */
     protected int buildConnectionCapabilities() {
@@ -623,16 +591,12 @@
     protected final void updateConnectionCapabilities() {
         int newCapabilities = buildConnectionCapabilities();
 
-        newCapabilities = changeCapability(newCapabilities,
-                CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, mRemoteVideoCapable);
-        newCapabilities = changeCapability(newCapabilities,
-                CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, mLocalVideoCapable);
+        newCapabilities = applyOriginalConnectionCapabilities(newCapabilities);
         newCapabilities = changeCapability(newCapabilities,
                 CAPABILITY_HIGH_DEF_AUDIO, mHasHighDefAudio);
         newCapabilities = changeCapability(newCapabilities, CAPABILITY_WIFI, mIsWifi);
         newCapabilities = changeCapability(newCapabilities, CAPABILITY_CAN_PAUSE_VIDEO,
-                mIsVideoPauseSupported && mRemoteVideoCapable && mLocalVideoCapable);
-
+                mIsVideoPauseSupported && isVideoCapable());
         newCapabilities = applyConferenceTerminationCapabilities(newCapabilities);
 
         if (getConnectionCapabilities() != newCapabilities) {
@@ -687,8 +651,7 @@
 
         // Set video state and capabilities
         setVideoState(mOriginalConnection.getVideoState());
-        setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
-        setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
+        setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
         setWifi(mOriginalConnection.isWifi());
         setVideoProvider(mOriginalConnection.getVideoProvider());
         setAudioQuality(mOriginalConnection.getAudioQuality());
@@ -1077,10 +1040,24 @@
     }
 
     /**
-     * Applies capabilities specific to conferences termination to the
-     * {@code CallCapabilities} bit-mask.
+     * Determines if the current connection is video capable.
      *
-     * @param capabilities The {@code CallCapabilities} bit-mask.
+     * A connection is deemed to be video capable if the original connection capabilities state that
+     * both local and remote video is supported.
+     *
+     * @return {@code true} if the connection is video capable, {@code false} otherwise.
+     */
+    private boolean isVideoCapable() {
+        return can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
+                && can(mOriginalConnectionCapabilities,
+                Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+    }
+
+    /**
+     * Applies capabilities specific to conferences termination to the
+     * {@code ConnectionCapabilities} bit-mask.
+     *
+     * @param capabilities The {@code ConnectionCapabilities} bit-mask.
      * @return The capabilities with the IMS conference capabilities applied.
      */
     private int applyConferenceTerminationCapabilities(int capabilities) {
@@ -1097,43 +1074,42 @@
     }
 
     /**
-     * Returns the local video capability state for the connection.
+     * Stores the new original connection capabilities, and applies them to the current connection,
+     * notifying any listeners as necessary.
      *
-     * @return {@code True} if the connection has local video capabilities.
+     * @param connectionCapabilities The original connection capabilties.
      */
-    public boolean isLocalVideoCapable() {
-        return mLocalVideoCapable;
-    }
-
-    /**
-     * Returns the remote video capability state for the connection.
-     *
-     * @return {@code True} if the connection has remote video capabilities.
-     */
-    public boolean isRemoteVideoCapable() {
-        return mRemoteVideoCapable;
-    }
-
-    /**
-     * Sets whether video capability is present locally.  Used during rebuild of the
-     * capabilities to set the video call capabilities.
-     *
-     * @param capable {@code True} if video capable.
-     */
-    public void setLocalVideoCapable(boolean capable) {
-        mLocalVideoCapable = capable;
+    public void setOriginalConnectionCapabilities(int connectionCapabilities) {
+        mOriginalConnectionCapabilities = connectionCapabilities;
         updateConnectionCapabilities();
     }
 
     /**
-     * Sets whether video capability is present remotely.  Used during rebuild of the
-     * capabilities to set the video call capabilities.
+     * Called to apply the capabilities present in the {@link #mOriginalConnection} to this
+     * {@link Connection}.  Provides a mapping between the capabilities present in the original
+     * connection (see {@link com.android.internal.telephony.Connection.Capability}) and those in
+     * this {@link Connection}.
      *
-     * @param capable {@code True} if video capable.
+     * @param capabilities The capabilities bitmask from the {@link Connection}.
+     * @return the capabilities bitmask with the original connection capabilities remapped and
+     *      applied.
      */
-    public void setRemoteVideoCapable(boolean capable) {
-        mRemoteVideoCapable = capable;
-        updateConnectionCapabilities();
+    public int applyOriginalConnectionCapabilities(int capabilities) {
+        // We only support downgrading to audio if both the remote and local side support
+        // downgrading to audio.
+        boolean supportsDowngradeToAudio = can(mOriginalConnectionCapabilities,
+                Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL |
+                        Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE);
+        capabilities = changeCapability(capabilities,
+                CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO, !supportsDowngradeToAudio);
+
+        capabilities = changeCapability(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
+                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
+
+        capabilities = changeCapability(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
+                can(mOriginalConnectionCapabilities, Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+
+        return capabilities;
     }
 
     /**
diff --git a/tests/Android.mk b/tests/Android.mk
index a3a657b..6cc0355 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -27,4 +27,8 @@
 
 LOCAL_INSTRUMENTATION_FOR := TeleService
 
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        android-support-test \
+        mockito-target
+
 include $(BUILD_PACKAGE)
diff --git a/tests/src/com/android/phone/MockitoHelper.java b/tests/src/com/android/phone/MockitoHelper.java
new file mode 100644
index 0000000..3da5d6e
--- /dev/null
+++ b/tests/src/com/android/phone/MockitoHelper.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.phone;
+
+import com.android.services.telephony.Log;
+
+/**
+ * Helper for Mockito-based test cases.
+ */
+public final class MockitoHelper {
+
+    private static final String TAG = "MockitoHelper";
+
+    private ClassLoader mOriginalClassLoader;
+    private Thread mContextThread;
+
+    /**
+     * Creates a new helper, which in turn will set the context classloader so it can load Mockito
+     * resources.
+     *
+     * @param packageClass test case class
+     */
+    public void setUp(Class<?> packageClass) throws Exception {
+        // makes a copy of the context classloader
+        mContextThread = Thread.currentThread();
+        mOriginalClassLoader = mContextThread.getContextClassLoader();
+        ClassLoader newClassLoader = packageClass.getClassLoader();
+        Log.v(TAG, "Changing context classloader from " + mOriginalClassLoader
+                + " to " + newClassLoader);
+        mContextThread.setContextClassLoader(newClassLoader);
+    }
+
+    /**
+     * Restores the context classloader to the previous value.
+     */
+    public void tearDown() throws Exception {
+        Log.v(TAG, "Restoring context classloader to " + mOriginalClassLoader);
+        mContextThread.setContextClassLoader(mOriginalClassLoader);
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/phone/common/mail/MailTransportTest.java b/tests/src/com/android/phone/common/mail/MailTransportTest.java
new file mode 100644
index 0000000..6acd517
--- /dev/null
+++ b/tests/src/com/android/phone/common/mail/MailTransportTest.java
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2016 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.phone.common.mail;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.Network;
+import android.test.AndroidTestCase;
+
+import com.android.phone.MockitoHelper;
+import com.android.phone.common.mail.MailTransport.SocketCreator;
+import com.android.phone.common.mail.store.ImapStore;
+import com.android.phone.vvm.omtp.imap.ImapHelper;
+
+import junit.framework.AssertionFailedError;
+
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import javax.net.SocketFactory;
+
+public class MailTransportTest extends AndroidTestCase {
+
+    private static final String HOST_ADDRESS = "127.0.0.1";
+    private static final String INVALID_HOST_ADDRESS = "255.255.255.255";
+    private static final int HOST_PORT = 80;
+    private static final int HOST_FLAGS = 0;
+    // bypass verifyHostname() in open() by setting ImapStore.FLAG_TRUST_ALL
+    private static final int HOST_FLAGS_SSL = ImapStore.FLAG_SSL & ImapStore.FLAG_TRUST_ALL;
+    private static final InetAddress VALID_INET_ADDRESS = createInetAddress(HOST_ADDRESS);
+    private static final InetAddress INVALID_INET_ADDRESS = createInetAddress(INVALID_HOST_ADDRESS);
+
+    // ClassLoader need to be replaced for mockito to work.
+    private MockitoHelper mMokitoHelper = new MockitoHelper();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mMokitoHelper.setUp(getClass());
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mMokitoHelper.tearDown();
+        super.tearDown();
+    }
+
+    public void testCreateSocket_anyNetwork() throws MessagingException {
+        // With no network, Socket#Socket() should be called.
+        MailTransport transport =
+                new MailTransport(getContext(), createMockImapHelper(), null, HOST_ADDRESS,
+                        HOST_PORT, HOST_FLAGS);
+        Socket socket = transport.createSocket();
+        assertTrue(socket != null);
+    }
+
+    public void testCreateSocket_networkSpecified() throws MessagingException, IOException {
+        // Network#getSocketFactory should be used to create socket.
+        Network mockNetwork = createMockNetwork();
+        MailTransport transport =
+                new MailTransport(getContext(), createMockImapHelper(), mockNetwork, HOST_ADDRESS,
+                        HOST_PORT, HOST_FLAGS);
+        Socket socket = transport.createSocket();
+        assertTrue(socket != null);
+        verify(mockNetwork).getSocketFactory();
+    }
+
+    public void testCreateSocket_socketCreator() throws MessagingException, IOException {
+        // For testing purposes, how sockets are created can be overridden.
+        SocketCreator socketCreator = new SocketCreator() {
+
+            private final Socket mSocket = new Socket();
+
+            @Override
+            public Socket createSocket() {
+                return mSocket;
+            }
+        };
+
+        MailTransport transport = new
+                MailTransport(getContext(), createMockImapHelper(), null, HOST_ADDRESS, HOST_PORT,
+                HOST_FLAGS);
+
+        transport.setSocketCreator(socketCreator);
+
+        Socket socket = transport.createSocket();
+        assertTrue(socket == socketCreator.createSocket());
+    }
+
+    public void testOpen() throws MessagingException {
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null,
+                HOST_ADDRESS,
+                HOST_PORT, HOST_FLAGS);
+        transport.setSocketCreator(new TestSocketCreator());
+        transport.open();
+        assertTrue(transport.isOpen());
+
+    }
+
+    public void testOpen_Ssl() throws MessagingException {
+        //opening with ssl support.
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null,
+                HOST_ADDRESS, HOST_PORT, HOST_FLAGS_SSL);
+        transport.setSocketCreator(new TestSocketCreator());
+        transport.open();
+        assertTrue(transport.isOpen());
+
+    }
+
+    public void testOpen_MultiIp() throws MessagingException {
+        //In case of round robin DNS, try all resolved address until one succeeded.
+        Network network = createMultiIpMockNetwork();
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network,
+                HOST_ADDRESS,
+                HOST_PORT, HOST_FLAGS);
+        transport.setSocketCreator(new TestSocketCreator());
+        transport.open();
+        assertTrue(transport.isOpen());
+    }
+
+    public void testOpen_MultiIp_SSL() throws MessagingException {
+        Network network = createMultiIpMockNetwork();
+
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network,
+                HOST_ADDRESS,
+                HOST_PORT, HOST_FLAGS_SSL);
+        transport.setSocketCreator(new TestSocketCreator());
+        transport.open();
+        assertTrue(transport.isOpen());
+    }
+
+    public void testOpen_network_hostResolutionFailed() {
+        // Couldn't resolve host on the network. Open() should fail.
+        Network network = createMockNetwork();
+        try {
+            when(network.getAllByName(HOST_ADDRESS))
+                    .thenThrow(new UnknownHostException("host resolution failed"));
+        } catch (IOException e) {
+            //ignored
+        }
+
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network,
+                HOST_ADDRESS,
+                HOST_PORT, HOST_FLAGS);
+        try {
+            transport.open();
+            throw new AssertionFailedError("Should throw MessagingException");
+        } catch (MessagingException e) {
+            //expected
+        }
+        assertFalse(transport.isOpen());
+    }
+
+    public void testOpen_createSocketFailed() {
+        // Unable to create socket. Open() should fail.
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null,
+                HOST_ADDRESS,
+                HOST_PORT, HOST_FLAGS);
+        transport.setSocketCreator(new SocketCreator() {
+            @Override
+            public Socket createSocket() throws MessagingException {
+                throw new MessagingException("createSocket failed");
+            }
+        });
+        try {
+            transport.open();
+            throw new AssertionFailedError("Should throw MessagingException");
+        } catch (MessagingException e) {
+            //expected
+        }
+        assertFalse(transport.isOpen());
+    }
+
+    public void testOpen_network_createSocketFailed() {
+        // Unable to create socket. Open() should fail.
+
+        Network network = createOneIpMockNetwork();
+        SocketFactory mockSocketFactory = mock(SocketFactory.class);
+        try {
+            when(mockSocketFactory.createSocket())
+                    .thenThrow(new IOException("unable to create socket"));
+        } catch (IOException e) {
+            //ignored
+        }
+        when(network.getSocketFactory()).thenReturn(mockSocketFactory);
+
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network,
+                HOST_ADDRESS, HOST_PORT, HOST_FLAGS);
+
+        try {
+            transport.open();
+            throw new AssertionFailedError("Should throw MessagingException");
+        } catch (MessagingException e) {
+            //expected
+        }
+        assertFalse(transport.isOpen());
+    }
+
+    public void testOpen_connectFailed_one() {
+        // There is only one IP for this host, and we failed to connect to it. Open() should fail.
+
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null,
+                HOST_ADDRESS, HOST_PORT, HOST_FLAGS);
+        transport.setSocketCreator(new SocketCreator() {
+            @Override
+            public Socket createSocket() throws MessagingException {
+                return new Socket() {
+                    @Override
+                    public void connect(SocketAddress address, int timeout) throws IOException {
+                        throw new IOException("connect failed");
+                    }
+                };
+            }
+        });
+        try {
+            transport.open();
+            throw new AssertionFailedError("Should throw MessagingException");
+        } catch (MessagingException e) {
+            //expected
+        }
+        assertFalse(transport.isOpen());
+    }
+
+    public void testOpen_connectFailed_multi() {
+        // There are multiple IP for this host, and we failed to connect to any of it.
+        // Open() should fail.
+        MailTransport transport = new MailTransport(getContext(), createMockImapHelper(),
+                createMultiIpMockNetwork(), HOST_ADDRESS, HOST_PORT, HOST_FLAGS);
+        transport.setSocketCreator(new SocketCreator() {
+            @Override
+            public Socket createSocket() throws MessagingException {
+                return new Socket() {
+                    @Override
+                    public void connect(SocketAddress address, int timeout) throws IOException {
+                        throw new IOException("connect failed");
+                    }
+                };
+            }
+        });
+        try {
+            transport.open();
+            throw new AssertionFailedError("Should throw MessagingException");
+        } catch (MessagingException e) {
+            //expected
+        }
+        assertFalse(transport.isOpen());
+    }
+
+    private class TestSocket extends Socket {
+
+        boolean mConnected = false;
+
+
+        /**
+         * A make a mock connection to the address.
+         *
+         * @param address Only address equivalent to VALID_INET_ADDRESS or INVALID_INET_ADDRESS is
+         * accepted
+         * @param timeout Ignored but should >= 0.
+         */
+        @Override
+        public void connect(SocketAddress address, int timeout) throws IOException {
+            // copied from Socket#connect
+            if (isClosed()) {
+                throw new SocketException("Socket is closed");
+            }
+            if (timeout < 0) {
+                throw new IllegalArgumentException("timeout < 0");
+            }
+            if (isConnected()) {
+                throw new SocketException("Already connected");
+            }
+            if (address == null) {
+                throw new IllegalArgumentException("remoteAddr == null");
+            }
+
+            if (!(address instanceof InetSocketAddress)) {
+                throw new AssertionError("address should be InetSocketAddress");
+            }
+
+            InetSocketAddress inetSocketAddress = (InetSocketAddress) address;
+            if (inetSocketAddress.getAddress().equals(INVALID_INET_ADDRESS)) {
+                throw new IOException("invalid address");
+            } else if (inetSocketAddress.getAddress().equals(VALID_INET_ADDRESS)) {
+                mConnected = true;
+            } else {
+                throw new AssertionError("Only INVALID_ADDRESS or VALID_ADDRESS are allowed");
+            }
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            return null;
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return null;
+        }
+
+        @Override
+        public boolean isConnected() {
+            return mConnected;
+        }
+
+    }
+
+
+    private class TestSocketCreator implements MailTransport.SocketCreator {
+
+        @Override
+        public Socket createSocket() throws MessagingException {
+            Socket socket = new TestSocket();
+            return socket;
+        }
+
+    }
+
+    private ImapHelper createMockImapHelper() {
+        return mock(ImapHelper.class);
+    }
+
+    /**
+     * @return a mock Network that can create a TestSocket with {@code getSocketFactory()
+     * .createSocket()}
+     */
+    private Network createMockNetwork() {
+        Network network = mock(Network.class);
+        SocketFactory mockSocketFactory = mock(SocketFactory.class);
+        try {
+            when(mockSocketFactory.createSocket()).thenReturn(new TestSocket());
+        } catch (IOException e) {
+            //ignored
+        }
+        when(network.getSocketFactory()).thenReturn(mockSocketFactory);
+        return network;
+    }
+
+    /**
+     * @return a mock Network like {@link MailTransportTest#createMockNetwork()}, but also supports
+     * {@link Network#getAllByName(String)} with one valid result.
+     */
+    private Network createOneIpMockNetwork() {
+        Network network = createMockNetwork();
+        try {
+            when(network.getAllByName(HOST_ADDRESS))
+                    .thenReturn(new InetAddress[] {VALID_INET_ADDRESS});
+        } catch (UnknownHostException e) {
+            //ignored
+        }
+
+        return network;
+    }
+
+    /**
+     * @return a mock Network like {@link MailTransportTest#createMockNetwork()}, but also supports
+     * {@link Network#getAllByName(String)}, which will return 2 address with the first one
+     * invalid.
+     */
+    private Network createMultiIpMockNetwork() {
+        Network network = createMockNetwork();
+        try {
+            when(network.getAllByName(HOST_ADDRESS))
+                    .thenReturn(new InetAddress[] {INVALID_INET_ADDRESS, VALID_INET_ADDRESS});
+        } catch (UnknownHostException e) {
+            //ignored
+        }
+
+        return network;
+    }
+
+    /**
+     * helper method to translate{@code host} into a InetAddress.
+     *
+     * @param host IP address of the host. Domain name should not be used as this method should not
+     * access the internet.
+     */
+    private static InetAddress createInetAddress(String host) {
+        try {
+            return InetAddress.getByName(host);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+
+}