Merge "IMS Bug Fixes" into nyc-mr1-dev
diff --git a/proguard.flags b/proguard.flags
index c4af490..41e26a1 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1 +1,6 @@
+# Keep classes and methods that have the guava @VisibleForTesting annotation
+-keep @**.VisibleForTesting class *
+-keepclassmembers class * {
+@**.VisibleForTesting *;
+}
 -verbose
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
new file mode 100644
index 0000000..5d120b8
--- /dev/null
+++ b/res/xml/vvm_config.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<list name="carrier_config_list">
+  <pbundle_as_map>
+    <!-- Orange France -->
+    <string-array name="mccmnc">
+      <item value="20801"/>
+      <item value="20802"/>
+    </string-array>
+
+    <int name="vvm_port_number_int" value="20481"/>
+    <string name="vvm_destination_number_string" value="21101"/>
+    <string-array name="carrier_vvm_package_name_string_array">
+      <item value="com.orange.vvm"/>
+    </string-array>
+    <string name="vvm_type_string" value="vvm_type_omtp"/>
+    <boolean name="vvm_cellular_data_required_bool" value="true"/>
+  </pbundle_as_map>
+
+  <pbundle_as_map>
+    <!-- Orange UK -->
+    <string-array name="mccmnc">
+      <item value="23433"/>
+      <item value="23434"/>
+    </string-array>
+
+    <int name="vvm_port_number_int" value="20481"/>
+    <string name="vvm_destination_number_string" value="881"/>
+    <string name="vvm_type_string" value="vvm_type_omtp"/>
+  </pbundle_as_map>
+
+  <pbundle_as_map>
+    <!-- T-Mobile USA-->
+    <string-array name="mccmnc">
+      <item value="310160"/>
+      <item value="310200"/>
+      <item value="310210"/>
+      <item value="310220"/>
+      <item value="310230"/>
+      <item value="310240"/>
+      <item value="310250"/>
+      <item value="310260"/>
+      <item value="310270"/>
+      <item value="310300"/>
+      <item value="310310"/>
+      <item value="310490"/>
+      <item value="310530"/>
+      <item value="310590"/>
+      <item value="310640"/>
+      <item value="310660"/>
+      <item value="310800"/>
+    </string-array>
+
+    <int name="vvm_port_number_int" value="1808"/>
+    <string name="vvm_destination_number_string" value="122"/>
+    <string-array name="carrier_vvm_package_name_string_array">
+      <item value="com.tmobile.vvm.application"/>
+    </string-array>
+    <string name="vvm_type_string" value="vvm_type_cvvm"/>
+  </pbundle_as_map>
+</list>
\ No newline at end of file
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7738b77..9a9b6a8 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -3049,4 +3049,39 @@
         return VoicemailNotificationSettingsUtil.isVibrationEnabled(phone);
     }
 
+
+    /**
+     * Make sure either called from same process as self (phone) or IPC caller has read privilege.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceReadPrivilegedPermission() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                null);
+    }
+
+    /**
+     * Return the application ID for the app type.
+     *
+     * @param subId the subscription ID that this request applies to.
+     * @param appType the uicc app type.
+     * @return Application ID for specificied app type, or null if no uicc.
+     */
+    @Override
+    public String getAidForAppType(int subId, int appType) {
+        enforceReadPrivilegedPermission();
+        Phone phone = getPhone(subId);
+        if (phone == null) {
+            return null;
+        }
+        String aid = null;
+        try {
+            aid = UiccController.getInstance().getUiccCard(phone.getPhoneId())
+                    .getApplicationByType(appType).getAid();
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Not getting aid. Exception ex=" + e);
+        }
+        return aid;
+    }
+
 }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index aa62575..4dd7d0b 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -1853,10 +1853,14 @@
     }
 
     static boolean isInEmergencyCall(CallManager cm) {
-        for (Connection cn : cm.getActiveFgCall().getConnections()) {
-            if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
-                    cn.getAddress())) {
-                return true;
+        Call fgCall = cm.getActiveFgCall();
+        // isIdle includes checks for the DISCONNECTING/DISCONNECTED state.
+        if(!fgCall.isIdle()) {
+            for (Connection cn : fgCall.getConnections()) {
+                if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(),
+                        cn.getAddress())) {
+                    return true;
+                }
             }
         }
         return false;
@@ -2412,7 +2416,7 @@
         // TODO: Should use some sort of special hidden flag to decorate this account as
         // an emergency-only account
         String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix +
-                String.valueOf(phone.getIccSerialNumber());
+                String.valueOf(phone.getFullIccSerialNumber());
         return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency);
     }
 
@@ -2466,7 +2470,7 @@
     private static Phone getPhoneFromIccId(String iccId) {
         if (!TextUtils.isEmpty(iccId)) {
             for (Phone phone : PhoneFactory.getPhones()) {
-                String phoneIccId = phone.getIccSerialNumber();
+                String phoneIccId = phone.getFullIccSerialNumber();
                 if (iccId.equals(phoneIccId)) {
                     return phone;
                 }
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index f452bab..7d5cc20 100644
--- a/src/com/android/phone/common/mail/MailTransport.java
+++ b/src/com/android/phone/common/mail/MailTransport.java
@@ -32,7 +32,6 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.net.SocketAddress;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -66,6 +65,7 @@
     private BufferedOutputStream mOut;
     private final int mFlags;
     private SocketCreator mSocketCreator;
+    private InetSocketAddress mAddress;
 
     public MailTransport(Context context, ImapHelper imapHelper, Network network, String address,
             int port, int flags) {
@@ -126,29 +126,21 @@
         while (socketAddresses.size() > 0) {
             mSocket = createSocket();
             try {
-                InetSocketAddress address = socketAddresses.remove(0);
-                mSocket.connect(address, SOCKET_CONNECT_TIMEOUT);
+                mAddress = socketAddresses.remove(0);
+                mSocket.connect(mAddress, SOCKET_CONNECT_TIMEOUT);
 
                 if (canTrySslSecurity()) {
-                    /**
-                     * {@link SSLSocket} must connect in its constructor, or create through a
-                     * already connected socket. Since we need to use
-                     * {@link Socket#connect(SocketAddress, int) } to set timeout, we can only
-                     * create it here.
+                    /*
+                    SSLSocket cannot be created with a connection timeout, so instead of doing a
+                    direct SSL connection, we connect with a normal connection and upgrade it into
+                    SSL
                      */
-                    LogUtils.d(TAG, "open: converting to SSL socket");
-                    mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
-                            .createSocket(mSocket, address.getHostName(), address.getPort(), true);
-                    // After the socket connects to an SSL server, confirm that the hostname is as
-                    // expected
-                    if (!canTrustAllCertificates()) {
-                        verifyHostname(mSocket, mHost);
-                    }
+                    reopenTls();
+                } else {
+                    mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+                    mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+                    mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
                 }
-
-                mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
-                mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
-                mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
                 success = true;
                 return;
             } catch (IOException ioe) {
@@ -205,6 +197,32 @@
     }
 
     /**
+     * Attempts to reopen a normal connection into a TLS connection.
+     */
+    public void reopenTls() throws MessagingException {
+        try {
+            LogUtils.d(TAG, "open: converting to TLS socket");
+            mSocket = HttpsURLConnection.getDefaultSSLSocketFactory()
+                    .createSocket(mSocket, mAddress.getHostName(), mAddress.getPort(), true);
+            // After the socket connects to an SSL server, confirm that the hostname is as
+            // expected
+            if (!canTrustAllCertificates()) {
+                verifyHostname(mSocket, mHost);
+            }
+            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
+            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
+            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
+
+        } catch (SSLException e) {
+            LogUtils.d(TAG, e.toString());
+            throw new CertificateValidationException(e.getMessage(), e);
+        } catch (IOException ioe) {
+            LogUtils.d(TAG, ioe.toString());
+            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
+        }
+    }
+
+    /**
      * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
      * service but is not in the public API.
      *
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index 9207aa9..58f0f76 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -17,21 +17,23 @@
 
 import android.provider.VoicemailContract.Status;
 import android.text.TextUtils;
+import android.util.ArraySet;
 
 import com.android.phone.common.mail.AuthenticationFailedException;
 import com.android.phone.common.mail.CertificateValidationException;
 import com.android.phone.common.mail.MailTransport;
 import com.android.phone.common.mail.MessagingException;
+import com.android.phone.common.mail.store.ImapStore.ImapException;
 import com.android.phone.common.mail.store.imap.ImapConstants;
 import com.android.phone.common.mail.store.imap.ImapResponse;
 import com.android.phone.common.mail.store.imap.ImapResponseParser;
 import com.android.phone.common.mail.store.imap.ImapUtility;
 import com.android.phone.common.mail.utils.LogUtils;
-import com.android.phone.common.mail.store.ImapStore.ImapException;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.net.ssl.SSLException;
@@ -46,6 +48,7 @@
     private ImapStore mImapStore;
     private MailTransport mTransport;
     private ImapResponseParser mParser;
+    private Set<String> mCapabilities = new ArraySet<>();
 
     static final String IMAP_REDACTED_LOG = "[IMAP command redacted]";
 
@@ -102,6 +105,22 @@
 
             createParser();
 
+            // The server should greet us with something like
+            // * OK IMAP4rev1 Server
+            // consume the response before doing anything else.
+            ImapResponse response = mParser.readResponse();
+            if (!response.isOk()) {
+                mImapStore.getImapHelper()
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+                throw new MessagingException(
+                        MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
+                        "Invalid server initial response");
+            }
+
+            queryCapability();
+
+            maybeDoStartTls();
+
             // LOGIN
             doLogin();
         } catch (SSLException e) {
@@ -133,6 +152,21 @@
     }
 
     /**
+     * Attempts to convert the connection into secure connection.
+     */
+    private void maybeDoStartTls() throws IOException, MessagingException {
+        // STARTTLS is required in the OMTP standard but not every implementation support it.
+        // Make sure the server does have this capability
+        if (hasCapability(ImapConstants.CAPABILITY_STARTTLS)) {
+            executeSimpleCommand(ImapConstants.STARTTLS);
+            mTransport.reopenTls();
+            createParser();
+            // The cached capabilities should be refreshed after TLS is established.
+            queryCapability();
+        }
+    }
+
+    /**
      * Logs into the IMAP server
      */
     private void doLogin() throws IOException, MessagingException, AuthenticationFailedException {
@@ -157,6 +191,24 @@
         }
     }
 
+    private void queryCapability() throws IOException, MessagingException {
+        List<ImapResponse> responses = executeSimpleCommand(ImapConstants.CAPABILITY);
+        mCapabilities.clear();
+        for (ImapResponse response : responses) {
+            if (response.isTagged()) {
+                continue;
+            }
+            for (int i = 0; i < response.size(); i++) {
+                mCapabilities.add(response.getStringOrEmpty(i).getString());
+            }
+        }
+
+        LogUtils.d(TAG, "Capabilities: " + mCapabilities.toString());
+    }
+
+    private boolean hasCapability(String capability) {
+        return mCapabilities.contains(capability);
+    }
     /**
      * Create an {@link ImapResponseParser} from {@code mTransport.getInputStream()} and
      * set it to {@link #mParser}.
diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
index 63dda8c..a04d584 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
@@ -109,4 +109,9 @@
     public static final String EXPIRED = "EXPIRED";
     public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
     public static final String UNAVAILABLE = "UNAVAILABLE";
+
+    /**
+     * capabilities
+     */
+    public static final String CAPABILITY_STARTTLS = "STARTTLS";
 }
\ No newline at end of file
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 25c0f95..77de3f5 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -306,8 +306,8 @@
                     retval = isSim1 ? -1 : 1;
                 }
 
-                int subId1 = PhoneUtils.getSubIdForPhoneAccount(account1);
-                int subId2 = PhoneUtils.getSubIdForPhoneAccount(account2);
+                int subId1 = mTelephonyManager.getSubIdForPhoneAccount(account1);
+                int subId2 = mTelephonyManager.getSubIdForPhoneAccount(account2);
                 if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
                         subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                     retval = (mSubscriptionManager.getSlotId(subId1) <
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 9534a10..81db684 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.phone.vvm.omtp;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.PersistableBundle;
@@ -23,47 +24,112 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.vvm.omtp.sms.OmtpCvvmMessageSender;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 import com.android.phone.vvm.omtp.sms.OmtpStandardMessageSender;
 
+import java.util.Arrays;
+import java.util.Set;
+
 /**
- * Handle activation and deactivation of a visual voicemail source. This class is necessary to
- * retrieve carrier vvm configuration details before sending the appropriate texts.
+ * Manages carrier dependent visual voicemail configuration values.
+ * The primary source is the value retrieved from CarrierConfigManager. If CarrierConfigManager does
+ * not provide the config (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value
+ * hardcoded in telephony will be used (in res/xml/vvm_config.xml)
+ *
+ * Hidden configs are new configs that are planned for future APIs, or miscellaneous settings that
+ * may clutter CarrierConfigManager too much.
+ *
+ * The current hidden configs are:
+ * {@link #getSslPort()}
+ * {@link #getDisabledCapabilities()}
  */
 public class OmtpVvmCarrierConfigHelper {
 
     private static final String TAG = "OmtpVvmCarrierCfgHlpr";
-    private Context mContext;
-    private int mSubId;
-    private PersistableBundle mCarrierConfig;
-    private String mVvmType;
+
+    static final String KEY_VVM_TYPE_STRING = CarrierConfigManager.KEY_VVM_TYPE_STRING;
+    static final String KEY_VVM_DESTINATION_NUMBER_STRING =
+            CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING;
+    static final String KEY_VVM_PORT_NUMBER_INT =
+            CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT;
+    static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING =
+            CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING;
+    static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+            "carrier_vvm_package_name_string_array";
+    static final String KEY_VVM_PREFETCH_BOOL =
+            CarrierConfigManager.KEY_VVM_PREFETCH_BOOL;
+    static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL =
+            CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+    static final String KEY_VVM_SSL_PORT_NUMBER_INT =
+            "vvm_ssl_port_number_int";
+    static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+            "vvm_disabled_capabilities_string_array";
+
+    private final Context mContext;
+    private final int mSubId;
+    private final PersistableBundle mCarrierConfig;
+    private final String mVvmType;
+
+    private final PersistableBundle mTelephonyConfig;
 
     public OmtpVvmCarrierConfigHelper(Context context, int subId) {
         mContext = context;
         mSubId = subId;
         mCarrierConfig = getCarrierConfig();
+
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mTelephonyConfig = new TelephonyVvmConfigManager(context.getResources())
+                .getConfig(telephonyManager.getNetworkOperator(subId));
+
         mVvmType = getVvmType();
     }
 
-    public String getVvmType() {
-        if (mCarrierConfig == null) {
-            return null;
-        }
-
-        return mCarrierConfig.getString(
-                CarrierConfigManager.KEY_VVM_TYPE_STRING, null);
+    @VisibleForTesting
+    OmtpVvmCarrierConfigHelper(PersistableBundle carrierConfig,
+            PersistableBundle telephonyConfig) {
+        mContext = null;
+        mSubId = 0;
+        mCarrierConfig = carrierConfig;
+        mTelephonyConfig = telephonyConfig;
+        mVvmType = getVvmType();
     }
 
-    public String getCarrierVvmPackageName() {
-        if (mCarrierConfig == null) {
+    @Nullable
+    public String getVvmType() {
+        return (String) getValue(KEY_VVM_TYPE_STRING);
+    }
+
+    @Nullable
+    public Set<String> getCarrierVvmPackageNames() {
+        Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
+        if (names != null) {
+            return names;
+        }
+        return getCarrierVvmPackageNames(mTelephonyConfig);
+    }
+
+    private static Set<String> getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) {
+        if (bundle == null) {
             return null;
         }
-
-        return mCarrierConfig.getString(
-                CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING, null);
+        Set<String> names = new ArraySet<>();
+        if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING)) {
+            names.add(bundle.getString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING));
+        }
+        if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)) {
+            names.addAll(Arrays.asList(
+                    bundle.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)));
+        }
+        if (names.isEmpty()) {
+            return null;
+        }
+        return names;
     }
 
     public boolean isOmtpVvmType() {
@@ -76,33 +142,83 @@
      * so by checking if the carrier's voicemail app is installed.
      */
     public boolean isEnabledByDefault() {
-        String packageName = mCarrierConfig.getString(
-                CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING);
-        if (packageName == null) {
-            return true;
+        for (String packageName : getCarrierVvmPackageNames()) {
+            try {
+                mContext.getPackageManager().getPackageInfo(packageName, 0);
+                return false;
+            } catch (NameNotFoundException e) {
+                // Do nothing.
+            }
         }
-        try {
-            mContext.getPackageManager().getPackageInfo(packageName, 0);
-            return false;
-        } catch (NameNotFoundException e) {
-            return true;
-        }
+        return true;
     }
 
     public boolean isCellularDataRequired() {
-        if (mCarrierConfig == null) {
-            return false;
-        }
-        return mCarrierConfig
-                .getBoolean(CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL);
+        return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL);
     }
 
     public boolean isPrefetchEnabled() {
-        if (mCarrierConfig == null) {
-            return false;
+        return (boolean) getValue(KEY_VVM_PREFETCH_BOOL);
+    }
+
+
+    public int getApplicationPort() {
+        Integer port = (Integer) getValue(KEY_VVM_PORT_NUMBER_INT);
+        if (port != null) {
+            return port;
         }
-        return mCarrierConfig
-                .getBoolean(CarrierConfigManager.KEY_VVM_PREFETCH_BOOL);
+        return 0;
+    }
+
+    @Nullable
+    public String getDestinationNumber() {
+        return (String) getValue(KEY_VVM_DESTINATION_NUMBER_STRING);
+    }
+
+    /**
+     * Hidden config.
+     *
+     * @return Port to start a SSL IMAP connection directly.
+     *
+     * TODO: make config public and add to CarrierConfigManager
+     */
+    @VisibleForTesting // TODO: remove after method used.
+    public int getSslPort() {
+        Integer port = (Integer) getValue(KEY_VVM_SSL_PORT_NUMBER_INT);
+        if (port != null) {
+            return port;
+        }
+        return 0;
+    }
+
+    /**
+     * Hidden Config.
+     *
+     * @return A set of capabilities that is reported by the IMAP CAPABILITY command, but determined
+     * to have issues and should not be used.
+     */
+    @VisibleForTesting // TODO: remove after method used.
+    @Nullable
+    public Set<String> getDisabledCapabilities() {
+        Set<String> disabledCapabilities = getDisabledCapabilities(mCarrierConfig);
+        if (disabledCapabilities != null) {
+            return disabledCapabilities;
+        }
+        return getDisabledCapabilities(mTelephonyConfig);
+    }
+
+    @Nullable
+    private static Set<String> getDisabledCapabilities(@Nullable PersistableBundle bundle) {
+        if (bundle == null) {
+            return null;
+        }
+        if (!bundle.containsKey(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)) {
+            return null;
+        }
+        ArraySet<String> result = new ArraySet<String>();
+        result.addAll(
+                Arrays.asList(bundle.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)));
+        return result;
     }
 
     public void startActivation() {
@@ -121,6 +237,7 @@
         }
     }
 
+    @Nullable
     private PersistableBundle getCarrierConfig() {
         if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
             Log.w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
@@ -134,19 +251,23 @@
             return null;
         }
 
-        return carrierConfigManager.getConfigForSubId(mSubId);
+        PersistableBundle config = carrierConfigManager.getConfigForSubId(mSubId);
+
+        if (TextUtils.isEmpty(config.getString(CarrierConfigManager.KEY_VVM_TYPE_STRING))) {
+            Log.w(TAG, "Carrier config missing VVM type, ignoring.");
+            return null;
+        }
+        return config;
     }
 
     private OmtpMessageSender getMessageSender() {
-        if (mCarrierConfig == null) {
+        if (mCarrierConfig == null && mTelephonyConfig == null) {
             Log.w(TAG, "Empty carrier config.");
             return null;
         }
 
-        int applicationPort = mCarrierConfig.getInt(
-                CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT, 0);
-        String destinationNumber = mCarrierConfig.getString(
-                CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING);
+        int applicationPort = getApplicationPort();
+        String destinationNumber = getDestinationNumber();
         if (TextUtils.isEmpty(destinationNumber)) {
             Log.w(TAG, "No destination number for this carrier.");
             return null;
@@ -169,4 +290,22 @@
 
         return messageSender;
     }
+
+    @Nullable
+    private Object getValue(String key) {
+        Object result;
+        if (mCarrierConfig != null) {
+            result = mCarrierConfig.get(key);
+            if (result != null) {
+                return result;
+            }
+        }
+        if (mTelephonyConfig != null) {
+            result = mTelephonyConfig.get(key);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
new file mode 100644
index 0000000..3a1967f
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManager.java
@@ -0,0 +1,153 @@
+/*
+ * 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;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.PersistableBundle;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.R;
+import com.android.phone.vvm.omtp.utils.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Load and caches telephony vvm config from res/xml/vvm_config.xml
+ */
+public class TelephonyVvmConfigManager {
+
+    private static final String TAG = "TelephonyVvmCfgMgr";
+
+    private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
+
+    static final String KEY_MCCMNC = "mccmnc";
+
+    private static Map<String, PersistableBundle> sCachedConfigs;
+
+    private final Map<String, PersistableBundle> mConfigs;
+
+    public TelephonyVvmConfigManager(Resources resources) {
+        if (sCachedConfigs == null) {
+            sCachedConfigs = loadConfigs(resources.getXml(R.xml.vvm_config));
+        }
+        mConfigs = sCachedConfigs;
+    }
+
+    @VisibleForTesting
+    TelephonyVvmConfigManager(XmlPullParser parser) {
+        mConfigs = loadConfigs(parser);
+    }
+
+    @Nullable
+    public PersistableBundle getConfig(String mccMnc) {
+        return mConfigs.get(mccMnc);
+    }
+
+    private static Map<String, PersistableBundle> loadConfigs(XmlPullParser parser) {
+        Map<String, PersistableBundle> configs = new ArrayMap<>();
+        try {
+            ArrayList list = readBundleList(parser);
+            for (Object object : list) {
+                if (!(object instanceof PersistableBundle)) {
+                    throw new IllegalArgumentException("PersistableBundle expected, got " + object);
+                }
+                PersistableBundle bundle = (PersistableBundle) object;
+                String[] mccMncs = bundle.getStringArray(KEY_MCCMNC);
+                if (mccMncs == null) {
+                    throw new IllegalArgumentException("MCCMNC is null");
+                }
+                for (String mccMnc : mccMncs) {
+                    configs.put(mccMnc, bundle);
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+        return configs;
+    }
+
+    @Nullable
+    public static ArrayList readBundleList(XmlPullParser in) throws IOException,
+            XmlPullParserException {
+        final int outerDepth = in.getDepth();
+        int event;
+        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+            if (event == XmlPullParser.START_TAG) {
+                final String startTag = in.getName();
+                final String[] tagName = new String[1];
+                in.next();
+                return XmlUtils.readThisListXml(in, startTag, tagName,
+                        new MyReadMapCallback(), false);
+            }
+        }
+        return null;
+    }
+
+    public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
+            XmlPullParserException {
+        final int outerDepth = in.getDepth();
+        final String startTag = in.getName();
+        final String[] tagName = new String[1];
+        int event;
+        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+            if (event == XmlPullParser.START_TAG) {
+                ArrayMap<String, ?> map =
+                        XmlUtils.readThisArrayMapXml(in, startTag, tagName,
+                                new MyReadMapCallback());
+                PersistableBundle result = new PersistableBundle();
+                for (Entry<String, ?> entry : map.entrySet()) {
+                    Object value = entry.getValue();
+                    if (value instanceof Integer) {
+                        result.putInt(entry.getKey(), (int) value);
+                    } else if (value instanceof Boolean) {
+                        result.putBoolean(entry.getKey(), (boolean) value);
+                    } else if (value instanceof String) {
+                        result.putString(entry.getKey(), (String) value);
+                    } else if (value instanceof String[]) {
+                        result.putStringArray(entry.getKey(), (String[]) value);
+                    } else if (value instanceof PersistableBundle) {
+                        result.putPersistableBundle(entry.getKey(), (PersistableBundle) value);
+                    }
+                }
+                return result;
+            }
+        }
+        return PersistableBundle.EMPTY;
+    }
+
+    static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
+
+        @Override
+        public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+                throws XmlPullParserException, IOException {
+            if (TAG_PERSISTABLEMAP.equals(tag)) {
+                return restoreFromXml(in);
+            }
+            throw new XmlPullParserException("Unknown tag=" + tag);
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
index 0c4eb62..1767c6b 100644
--- a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
+++ b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
@@ -52,7 +52,10 @@
 
             OmtpVvmCarrierConfigHelper carrierConfigHelper = new OmtpVvmCarrierConfigHelper(
                     context, PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount));
-            if (packageName.equals(carrierConfigHelper.getCarrierVvmPackageName())) {
+            if (carrierConfigHelper.getCarrierVvmPackageNames() == null) {
+                continue;
+            }
+            if (carrierConfigHelper.getCarrierVvmPackageNames().contains(packageName)) {
                 VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(
                         context, phoneAccount, false, false);
                 OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
diff --git a/src/com/android/phone/vvm/omtp/utils/XmlUtils.java b/src/com/android/phone/vvm/omtp/utils/XmlUtils.java
new file mode 100644
index 0000000..4eeb5ce
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/utils/XmlUtils.java
@@ -0,0 +1,245 @@
+/*
+ * 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.utils;
+
+import android.util.ArrayMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class XmlUtils {
+
+    public static final ArrayMap<String, ?> readThisArrayMapXml(XmlPullParser parser, String endTag,
+            String[] name, ReadMapCallback callback)
+            throws XmlPullParserException, java.io.IOException {
+        ArrayMap<String, Object> map = new ArrayMap<>();
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                Object val = readThisValueXml(parser, name, callback, true);
+                map.put(name[0], val);
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return map;
+                }
+                throw new XmlPullParserException(
+                        "Expected " + endTag + " end tag at: " + parser.getName());
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+                "Document ended before " + endTag + " end tag");
+    }
+
+    /**
+     * Read an ArrayList object from an XmlPullParser.  The XML data could previously have been
+     * generated by writeListXml().  The XmlPullParser must be positioned <em>after</em> the tag
+     * that begins the list.
+     *
+     * @param parser The XmlPullParser from which to read the list data.
+     * @param endTag Name of the tag that will end the list, usually "list".
+     * @param name An array of one string, used to return the name attribute of the list's tag.
+     * @return HashMap The newly generated list.
+     */
+    public static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
+            String[] name, ReadMapCallback callback, boolean arrayMap)
+            throws XmlPullParserException, java.io.IOException {
+        ArrayList list = new ArrayList();
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                Object val = readThisValueXml(parser, name, callback, arrayMap);
+                list.add(val);
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return list;
+                }
+                throw new XmlPullParserException(
+                        "Expected " + endTag + " end tag at: " + parser.getName());
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+                "Document ended before " + endTag + " end tag");
+    }
+
+    /**
+     * Read a String[] object from an XmlPullParser.  The XML data could previously have been
+     * generated by writeStringArrayXml().  The XmlPullParser must be positioned <em>after</em> the
+     * tag that begins the list.
+     *
+     * @param parser The XmlPullParser from which to read the list data.
+     * @param endTag Name of the tag that will end the list, usually "string-array".
+     * @param name An array of one string, used to return the name attribute of the list's tag.
+     * @return Returns a newly generated String[].
+     */
+    public static String[] readThisStringArrayXml(XmlPullParser parser, String endTag,
+            String[] name) throws XmlPullParserException, java.io.IOException {
+
+        parser.next();
+
+        List<String> array = new ArrayList<>();
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    try {
+                        array.add(parser.getAttributeValue(null, "value"));
+                    } catch (NullPointerException e) {
+                        throw new XmlPullParserException("Need value attribute in item");
+                    } catch (NumberFormatException e) {
+                        throw new XmlPullParserException("Not a number in value attribute in item");
+                    }
+                } else {
+                    throw new XmlPullParserException("Expected item tag at: " + parser.getName());
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return array.toArray(new String[0]);
+                } else if (parser.getName().equals("item")) {
+
+                } else {
+                    throw new XmlPullParserException("Expected " + endTag + " end tag at: " +
+                            parser.getName());
+                }
+            }
+            eventType = parser.next();
+        } while (eventType != XmlPullParser.END_DOCUMENT);
+
+        throw new XmlPullParserException("Document ended before " + endTag + " end tag");
+    }
+
+    private static Object readThisValueXml(XmlPullParser parser, String[] name,
+            ReadMapCallback callback, boolean arrayMap)
+            throws XmlPullParserException, java.io.IOException {
+        final String valueName = parser.getAttributeValue(null, "name");
+        final String tagName = parser.getName();
+
+        Object res;
+
+        if (tagName.equals("null")) {
+            res = null;
+        } else if (tagName.equals("string")) {
+            String value = "";
+            int eventType;
+            while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("string")) {
+                        name[0] = valueName;
+                        return value;
+                    }
+                    throw new XmlPullParserException(
+                            "Unexpected end tag in <string>: " + parser.getName());
+                } else if (eventType == XmlPullParser.TEXT) {
+                    value += parser.getText();
+                } else if (eventType == XmlPullParser.START_TAG) {
+                    throw new XmlPullParserException(
+                            "Unexpected start tag in <string>: " + parser.getName());
+                }
+            }
+            throw new XmlPullParserException(
+                    "Unexpected end of document in <string>");
+        } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
+            // all work already done by readThisPrimitiveValueXml
+        } else if (tagName.equals("string-array")) {
+            res = readThisStringArrayXml(parser, "string-array", name);
+            name[0] = valueName;
+            return res;
+        } else if (tagName.equals("list")) {
+            parser.next();
+            res = readThisListXml(parser, "list", name, callback, arrayMap);
+            name[0] = valueName;
+            return res;
+        } else if (callback != null) {
+            res = callback.readThisUnknownObjectXml(parser, tagName);
+            name[0] = valueName;
+            return res;
+        } else {
+            throw new XmlPullParserException("Unknown tag: " + tagName);
+        }
+
+        // Skip through to end tag.
+        int eventType;
+        while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(tagName)) {
+                    name[0] = valueName;
+                    return res;
+                }
+                throw new XmlPullParserException(
+                        "Unexpected end tag in <" + tagName + ">: " + parser.getName());
+            } else if (eventType == XmlPullParser.TEXT) {
+                throw new XmlPullParserException(
+                        "Unexpected text in <" + tagName + ">: " + parser.getName());
+            } else if (eventType == XmlPullParser.START_TAG) {
+                throw new XmlPullParserException(
+                        "Unexpected start tag in <" + tagName + ">: " + parser.getName());
+            }
+        }
+        throw new XmlPullParserException(
+                "Unexpected end of document in <" + tagName + ">");
+    }
+
+    private static final Object readThisPrimitiveValueXml(XmlPullParser parser, String tagName)
+            throws XmlPullParserException, java.io.IOException {
+        try {
+            if (tagName.equals("int")) {
+                return Integer.parseInt(parser.getAttributeValue(null, "value"));
+            } else if (tagName.equals("long")) {
+                return Long.valueOf(parser.getAttributeValue(null, "value"));
+            } else if (tagName.equals("float")) {
+                return Float.valueOf(parser.getAttributeValue(null, "value"));
+            } else if (tagName.equals("double")) {
+                return Double.valueOf(parser.getAttributeValue(null, "value"));
+            } else if (tagName.equals("boolean")) {
+                return Boolean.valueOf(parser.getAttributeValue(null, "value"));
+            } else {
+                return null;
+            }
+        } catch (NullPointerException e) {
+            throw new XmlPullParserException("Need value attribute in <" + tagName + ">");
+        } catch (NumberFormatException e) {
+            throw new XmlPullParserException(
+                    "Not a number in value attribute in <" + tagName + ">");
+        }
+    }
+
+    public interface ReadMapCallback {
+
+        /**
+         * Called from readThisMapXml when a START_TAG is not recognized. The input stream is
+         * positioned within the start tag so that attributes can be read using in.getAttribute.
+         *
+         * @param in the XML input stream
+         * @param tag the START_TAG that was not recognized.
+         * @return the Object parsed from the stream which will be put into the map.
+         * @throws XmlPullParserException if the START_TAG is not recognized.
+         * @throws IOException on XmlPullParser serialization errors.
+         */
+        Object readThisUnknownObjectXml(XmlPullParser in, String tag)
+                throws XmlPullParserException, IOException;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 3ed8356..19b1d8a 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -71,6 +71,7 @@
         private boolean mIsVideoPresenceSupported;
         private boolean mIsVideoPauseSupported;
         private boolean mIsMergeCallSupported;
+        private boolean mIsVideoConferencingSupported;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
@@ -189,6 +190,7 @@
                 instantLetteringExtras = getPhoneAccountExtras();
             }
             mIsMergeCallSupported = isCarrierMergeCallSupported();
+            mIsVideoConferencingSupported = isCarrierVideoConferencingSupported();
 
             if (isEmergency && mContext.getResources().getBoolean(
                     R.bool.config_emergency_account_emergency_calls_only)) {
@@ -244,7 +246,8 @@
             // Check if IMS video pause is supported.
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
         }
 
         /**
@@ -256,7 +259,8 @@
         private boolean isCarrierVideoPresenceSupported() {
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL);
         }
 
         /**
@@ -267,7 +271,8 @@
         private boolean isCarrierInstantLetteringSupported() {
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b.getBoolean(CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL);
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL);
         }
 
         /**
@@ -278,7 +283,8 @@
         private boolean isCarrierMergeCallSupported() {
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b.getBoolean(CarrierConfigManager.KEY_SUPPORT_CONFERENCE_CALL_BOOL);
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_SUPPORT_CONFERENCE_CALL_BOOL);
         }
 
         /**
@@ -289,7 +295,20 @@
         private boolean isCarrierEmergencyVideoCallsAllowed() {
             PersistableBundle b =
                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
-            return b.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL);
+        }
+
+        /**
+         * Determines from carrier config whether video conferencing is supported.
+         *
+         * @return {@code true} if video conferencing is supported, {@code false} otherwise.
+         */
+        private boolean isCarrierVideoConferencingSupported() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return b != null &&
+                    b.getBoolean(CarrierConfigManager.KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL);
         }
 
         /**
@@ -339,6 +358,14 @@
         public boolean isMergeCallSupported() {
             return mIsMergeCallSupported;
         }
+
+        /**
+         * Indicates whether this account supports video conferencing.
+         * @return {@code true} if the account supports video conferencing, {@code false} otherwise.
+         */
+        public boolean isVideoConferencingSupported() {
+            return mIsVideoConferencingSupported;
+        }
     }
 
     private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -430,6 +457,22 @@
     }
 
     /**
+     * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} supports
+     * video conferencing.
+     *
+     * @param handle The {@link PhoneAccountHandle}.
+     * @return {@code True} if video conferencing is supported.
+     */
+    boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
+        for (AccountEntry entry : mAccounts) {
+            if (entry.getPhoneAccountHandle().equals(handle)) {
+                return entry.isVideoConferencingSupported();
+            }
+        }
+        return false;
+    }
+
+    /**
      * @return Reference to the {@code TelecomAccountRegistry}'s subscription manager.
      */
     SubscriptionManager getSubscriptionManager() {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 40e4ef9..c1d54d8 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -296,7 +296,7 @@
 
         final TelephonyConnection connection =
                 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
-                        request.getTelecomCallId(), request.getAddress());
+                        request.getTelecomCallId(), request.getAddress(), request.getVideoState());
         if (connection == null) {
             return Connection.createFailedConnection(
                     DisconnectCauseUtil.toTelecomDisconnectCause(
@@ -378,10 +378,15 @@
             return Connection.createCanceledConnection();
         }
 
+        // We should rely on the originalConnection to get the video state.  The request coming
+        // from Telecom does not know the video state of the incoming call.
+        int videoState = originalConnection != null ? originalConnection.getVideoState() :
+                VideoProfile.STATE_AUDIO_ONLY;
+
         Connection connection =
                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
                         request.getAccountHandle(), request.getTelecomCallId(),
-                        request.getAddress());
+                        request.getAddress(), videoState);
         if (connection == null) {
             return Connection.createCanceledConnection();
         } else {
@@ -478,11 +483,16 @@
             return Connection.createCanceledConnection();
         }
 
+        // We should rely on the originalConnection to get the video state.  The request coming
+        // from Telecom does not know the video state of the unknown call.
+        int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
+                VideoProfile.STATE_AUDIO_ONLY;
+
         TelephonyConnection connection =
                 createConnectionFor(phone, unknownConnection,
                         !unknownConnection.isIncoming() /* isOutgoing */,
                         request.getAccountHandle(), request.getTelecomCallId(),
-                        request.getAddress());
+                        request.getAddress(), videoState);
 
         if (connection == null) {
             return Connection.createCanceledConnection();
@@ -546,7 +556,8 @@
             boolean isOutgoing,
             PhoneAccountHandle phoneAccountHandle,
             String telecomCallId,
-            Uri address) {
+            Uri address,
+            int videoState) {
         TelephonyConnection returnConnection = null;
         int phoneType = phone.getPhoneType();
         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
@@ -564,9 +575,13 @@
                             phoneAccountHandle));
             boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber(
                     address.getSchemeSpecificPart()));
-            returnConnection.setConferenceSupported(!isEmergencyCall
-                    && TelecomAccountRegistry.getInstance(this).isMergeCallSupported(
-                            phoneAccountHandle));
+            boolean isVideoCall = VideoProfile.isVideo(videoState);
+            boolean isConferencingSupported = TelecomAccountRegistry.getInstance(this)
+                    .isMergeCallSupported(phoneAccountHandle);
+            boolean isVideoConferencingSupported = TelecomAccountRegistry.getInstance(this)
+                    .isVideoConferencingSupported(phoneAccountHandle);
+            returnConnection.setConferenceSupported(!isEmergencyCall && isConferencingSupported
+                    && (!isVideoCall || (isVideoCall && isVideoConferencingSupported)));
         }
         return returnConnection;
     }
diff --git a/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java b/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java
new file mode 100644
index 0000000..63c7f60
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelperTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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;
+
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DESTINATION_NUMBER_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PREFETCH_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_SSL_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_TYPE_STRING;
+
+import android.os.PersistableBundle;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class OmtpVvmCarrierConfigHelperTest extends TestCase {
+
+    private static final String CARRIER_TYPE = "omtp.carrier";
+    private static final String CARRIER_PACKAGE_NAME = "omtp.carrier.package";
+    private static final boolean CARRIER_CELLULAR_REQUIRED = false;
+    private static final boolean CARRIER_PREFETCH = true;
+    private static final String CARRIER_DESTINATION_NUMBER = "123";
+    private static final int CARRIER_APPLICATION_PORT = 456;
+    private static final int DEFAULT_SSL_PORT = 0;
+    private static final Set<String> DEFAULT_DISABLED_CAPABILITIES = null;
+
+    private static final String TELEPHONY_TYPE = "omtp.telephony";
+    private static final String[] TELEPHONY_PACKAGE_NAMES = {"omtp.telephony.package"};
+    private static final boolean TELEPHONY_CELLULAR_REQUIRED = true;
+    private static final boolean TELEPHONY_PREFETCH = false;
+    private static final String TELEPHONY_DESTINATION_NUMBER = "321";
+    private static final int TELEPHONY_APPLICATION_PORT = 654;
+    private static final int TELEPHONY_SSL_PORT = 997;
+    private static final String[] TELEPHONY_DISABLED_CAPABILITIES = {"foo"};
+
+    private OmtpVvmCarrierConfigHelper mHelper;
+
+    public void testCarrierConfig() {
+        mHelper = new OmtpVvmCarrierConfigHelper(createCarrierConfig(), null);
+        verifyCarrierConfig();
+        verifyDefaultExtraConfig();
+    }
+
+    public void testTelephonyConfig() {
+        mHelper = new OmtpVvmCarrierConfigHelper(null, createTelephonyConfig());
+        verifyTelephonyConfig();
+        verifyTelephonyExtraConfig();
+    }
+
+    public void testMixedConfig() {
+        mHelper = new OmtpVvmCarrierConfigHelper(createCarrierConfig(), createTelephonyConfig());
+        verifyCarrierConfig();
+        verifyTelephonyExtraConfig();
+    }
+
+    private PersistableBundle createCarrierConfig() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(KEY_VVM_TYPE_STRING, CARRIER_TYPE);
+        bundle.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING,
+                CARRIER_PACKAGE_NAME);
+        bundle.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL,
+                CARRIER_CELLULAR_REQUIRED);
+        bundle.putBoolean(KEY_VVM_PREFETCH_BOOL,
+                CARRIER_PREFETCH);
+        bundle.putString(KEY_VVM_DESTINATION_NUMBER_STRING,
+                CARRIER_DESTINATION_NUMBER);
+        bundle.putInt(KEY_VVM_PORT_NUMBER_INT, CARRIER_APPLICATION_PORT);
+        return bundle;
+    }
+
+    private void verifyCarrierConfig() {
+        assertEquals(CARRIER_TYPE, mHelper.getVvmType());
+        assertEquals(new HashSet<>(Arrays.asList(CARRIER_PACKAGE_NAME)),
+                mHelper.getCarrierVvmPackageNames());
+        assertEquals(CARRIER_CELLULAR_REQUIRED, mHelper.isCellularDataRequired());
+        assertEquals(CARRIER_PREFETCH, mHelper.isPrefetchEnabled());
+        assertEquals(CARRIER_APPLICATION_PORT, mHelper.getApplicationPort());
+        assertEquals(CARRIER_DESTINATION_NUMBER, mHelper.getDestinationNumber());
+    }
+
+
+    private void verifyDefaultExtraConfig() {
+        assertEquals(DEFAULT_SSL_PORT, mHelper.getSslPort());
+        assertEquals(DEFAULT_DISABLED_CAPABILITIES, mHelper.getDisabledCapabilities());
+    }
+
+
+    private PersistableBundle createTelephonyConfig() {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(KEY_VVM_TYPE_STRING, TELEPHONY_TYPE);
+        bundle.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY,
+                TELEPHONY_PACKAGE_NAMES);
+        bundle.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL,
+                TELEPHONY_CELLULAR_REQUIRED);
+        bundle.putBoolean(KEY_VVM_PREFETCH_BOOL,
+                TELEPHONY_PREFETCH);
+        bundle.putString(KEY_VVM_DESTINATION_NUMBER_STRING,
+                TELEPHONY_DESTINATION_NUMBER);
+        bundle.putInt(KEY_VVM_PORT_NUMBER_INT, TELEPHONY_APPLICATION_PORT);
+        bundle.putInt(KEY_VVM_SSL_PORT_NUMBER_INT, TELEPHONY_SSL_PORT);
+        bundle.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY,
+                TELEPHONY_DISABLED_CAPABILITIES);
+        return bundle;
+    }
+
+    private void verifyTelephonyConfig() {
+        assertEquals(TELEPHONY_TYPE, mHelper.getVvmType());
+        assertEquals(new HashSet<>(Arrays.asList(TELEPHONY_PACKAGE_NAMES)),
+                mHelper.getCarrierVvmPackageNames());
+        assertEquals(TELEPHONY_CELLULAR_REQUIRED, mHelper.isCellularDataRequired());
+        assertEquals(TELEPHONY_PREFETCH, mHelper.isPrefetchEnabled());
+        assertEquals(TELEPHONY_APPLICATION_PORT, mHelper.getApplicationPort());
+        assertEquals(TELEPHONY_DESTINATION_NUMBER, mHelper.getDestinationNumber());
+    }
+
+    private void verifyTelephonyExtraConfig() {
+        assertEquals(TELEPHONY_SSL_PORT, mHelper.getSslPort());
+        assertEquals(new HashSet<>(Arrays.asList(TELEPHONY_DISABLED_CAPABILITIES)),
+                mHelper.getDisabledCapabilities());
+    }
+}
diff --git a/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java b/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java
new file mode 100644
index 0000000..8e7a0da
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/TelephonyVvmConfigManagerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DESTINATION_NUMBER_STRING;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_PREFETCH_BOOL;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_SSL_PORT_NUMBER_INT;
+import static com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper.KEY_VVM_TYPE_STRING;
+
+import android.os.PersistableBundle;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.StringReader;
+import java.util.Arrays;
+
+public class TelephonyVvmConfigManagerTest extends TestCase {
+
+    private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+            + "<list name=\"carrier_config_list\">\n";
+    private static final String XML_FOOTER = "</list>";
+
+    private static final String CARRIER = "  <pbundle_as_map>\n"
+            + "    <string-array name=\"mccmnc\">\n"
+            + "      <item value=\"12345\"/>\n"
+            + "      <item value=\"67890\"/>\n"
+            + "    </string-array>\n"
+            + "    <int name=\"vvm_port_number_int\" value=\"54321\"/>\n"
+            + "    <string name=\"vvm_destination_number_string\">11111</string>\n"
+            + "    <string-array name=\"carrier_vvm_package_name_string_array\">\n"
+            + "      <item value=\"com.android.phone\"/>\n"
+            + "    </string-array>\n"
+            + "    <string name=\"vvm_type_string\">vvm_type_omtp</string>\n"
+            + "    <boolean name=\"vvm_cellular_data_required\" value=\"true\"/>\n"
+            + "    <boolean name=\"vvm_prefetch\" value=\"true\"/>\n"
+            + "    <int name=\"vvm_ssl_port_number_int\" value=\"997\"/>\n"
+            + "    <string-array name=\"vvm_disabled_capabilities_string_array\">\n"
+            + "      <item value =\"foo\"/>\n"
+            + "      <item value =\"bar\"/>\n"
+            + "    </string-array>\n"
+            + "  </pbundle_as_map>\n";
+
+    private static final String CARRIER_EMPTY = "<pbundle_as_map></pbundle_as_map>\n";
+
+
+    public void testLoadConfigFromXml() {
+        TelephonyVvmConfigManager manager = createManager(XML_HEADER + CARRIER + XML_FOOTER);
+        verifyCarrier(manager.getConfig("12345"));
+        verifyCarrier(manager.getConfig("67890"));
+    }
+
+    public void testLoadConfigFromXml_Multiple() {
+        TelephonyVvmConfigManager manager =
+                createManager(XML_HEADER + CARRIER + CARRIER + XML_FOOTER);
+        verifyCarrier(manager.getConfig("12345"));
+        verifyCarrier(manager.getConfig("67890"));
+    }
+
+    public void testLoadConfigFromXml_Empty() {
+        createManager(XML_HEADER + CARRIER_EMPTY + XML_FOOTER);
+    }
+
+
+    private void verifyCarrier(PersistableBundle config) {
+        assertTrue(Arrays.equals(new String[]{"12345", "67890"},
+                config.getStringArray(TelephonyVvmConfigManager.KEY_MCCMNC)));
+        assertEquals(54321, config.getInt(KEY_VVM_PORT_NUMBER_INT));
+        assertEquals("11111", config.getString(KEY_VVM_DESTINATION_NUMBER_STRING));
+        assertTrue(Arrays.equals(new String[]{"com.android.phone"},
+                config.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)));
+        assertEquals("vvm_type_omtp", config.getString(KEY_VVM_TYPE_STRING));
+        assertEquals(true, config.getBoolean(KEY_VVM_PREFETCH_BOOL));
+        assertEquals(true, config.getBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL));
+        assertEquals(997, config.getInt(KEY_VVM_SSL_PORT_NUMBER_INT));
+        assertTrue(Arrays.equals(new String[]{"foo", "bar"},
+                config.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)));
+    }
+
+    private TelephonyVvmConfigManager createManager(String xml) {
+        try {
+            XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+            parser.setInput(new StringReader(xml));
+            return new TelephonyVvmConfigManager(parser);
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}