Added configuration for VT downgrade/tear down when data disabled
[android/platform/frameworks/opt/net/ims.git] / src / java / com / android / ims / ImsManager.java
index f8f2bf6..aeb7ae6 100644 (file)
 package com.android.ims;
 
 import android.app.PendingIntent;
-import android.app.QueuedWork;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -34,13 +36,15 @@ import android.telephony.TelephonyManager;
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsEcbmListener;
+import com.android.ims.internal.IImsMultiEndpoint;
 import com.android.ims.internal.IImsRegistrationListener;
 import com.android.ims.internal.IImsService;
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.ImsCallSession;
 import com.android.ims.internal.IImsConfig;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 
 /**
@@ -62,6 +66,8 @@ public class ImsManager {
     public static final int PROPERTY_DBG_VT_AVAIL_OVERRIDE_DEFAULT = 0;
     public static final String PROPERTY_DBG_WFC_AVAIL_OVERRIDE = "persist.dbg.wfc_avail_ovr";
     public static final int PROPERTY_DBG_WFC_AVAIL_OVERRIDE_DEFAULT = 0;
+    public static final String PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE = "persist.dbg.allow_ims_off";
+    public static final int PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE_DEFAULT = 0;
 
     /**
      * For accessing the IMS related service.
@@ -139,6 +145,18 @@ public class ImsManager {
      */
     public static final String EXTRA_USSD = "android:ussd";
 
+    /**
+     * Part of the ACTION_IMS_INCOMING_CALL intents.
+     * A boolean value; Flag to indicate whether the call is an unknown
+     * dialing call. Such calls are originated by sending commands (like
+     * AT commands) directly to modem without Android involvement.
+     * Even though they are not incoming calls, they are propagated
+     * to Phone app using same ACTION_IMS_INCOMING_CALL intent.
+     * Internal use only.
+     * @hide
+     */
+    public static final String EXTRA_IS_UNKNOWN_CALL = "android:isUnknown";
+
     private static final String TAG = "ImsManager";
     private static final boolean DBG = true;
 
@@ -153,10 +171,26 @@ public class ImsManager {
     private ImsUt mUt = null;
     // Interface to get/set ims config items
     private ImsConfig mConfig = null;
+    private boolean mConfigUpdated = false;
+
+    private ImsConfigListener mImsConfigListener;
 
     // ECBM interface
     private ImsEcbm mEcbm = null;
 
+    private ImsMultiEndpoint mMultiEndpoint = null;
+
+    // SystemProperties used as cache
+    private static final String VOLTE_PROVISIONED_PROP = "net.lte.ims.volte.provisioned";
+    private static final String WFC_PROVISIONED_PROP = "net.lte.ims.wfc.provisioned";
+    private static final String VT_PROVISIONED_PROP = "net.lte.ims.vt.provisioned";
+    // Flag indicating data enabled or not. This flag should be in sync with
+    // DcTracker.isDataEnabled(). The flag will be set later during boot up.
+    private static final String DATA_ENABLED_PROP = "net.lte.ims.data.enabled";
+
+    public static final String TRUE = "true";
+    public static final String FALSE = "false";
+
     /**
      * Gets a manager instance.
      *
@@ -180,6 +214,12 @@ public class ImsManager {
      * Returns the user configuration of Enhanced 4G LTE Mode setting
      */
     public static boolean isEnhanced4gLteModeSettingEnabledByUser(Context context) {
+        // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true.
+        // If user changes SIM from editable mode to uneditable mode, need to return true.
+        if (!getBooleanCarrierConfig(context,
+                    CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
+            return true;
+        }
         int enabled = android.provider.Settings.Global.getInt(
                     context.getContentResolver(),
                     android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, ImsConfig.FeatureValueConstants.ON);
@@ -214,7 +254,7 @@ public class ImsManager {
      */
     public static boolean isNonTtyOrTtyOnVolteEnabled(Context context) {
         if (getBooleanCarrierConfig(context,
-                    CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
+                CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
             return true;
         }
 
@@ -232,40 +272,59 @@ public class ImsManager {
             return true;
         }
 
-        boolean disabledByGlobalSetting = android.provider.Settings.Global.getInt(
-                context.getContentResolver(),
-                android.provider.Settings.Global.VOLTE_FEATURE_DISABLED, 0) == 1;
-
         return context.getResources().getBoolean(
                 com.android.internal.R.bool.config_device_volte_available)
                 && getBooleanCarrierConfig(context,
                         CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
-                && !disabledByGlobalSetting;
+                && isGbaValid(context);
     }
 
-    /*
+    /**
      * Indicates whether VoLTE is provisioned on device
      */
     public static boolean isVolteProvisionedOnDevice(Context context) {
-        boolean isProvisioned = true;
         if (getBooleanCarrierConfig(context,
                     CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
-            isProvisioned = false; // disable on any error
             ImsManager mgr = ImsManager.getInstance(context,
                     SubscriptionManager.getDefaultVoicePhoneId());
             if (mgr != null) {
-                try {
-                    ImsConfig config = mgr.getConfigInterface();
-                    if (config != null) {
-                        isProvisioned = config.getVolteProvisioned();
-                    }
-                } catch (ImsException ie) {
-                    // do nothing
-                }
+                return mgr.isVolteProvisioned();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Indicates whether VoWifi is provisioned on device
+     */
+    public static boolean isWfcProvisionedOnDevice(Context context) {
+        if (getBooleanCarrierConfig(context,
+                CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
+            ImsManager mgr = ImsManager.getInstance(context,
+                    SubscriptionManager.getDefaultVoicePhoneId());
+            if (mgr != null) {
+                return mgr.isWfcProvisioned();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Indicates whether VT is provisioned on device
+     */
+    public static boolean isVtProvisionedOnDevice(Context context) {
+        if (getBooleanCarrierConfig(context,
+                CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
+            ImsManager mgr = ImsManager.getInstance(context,
+                    SubscriptionManager.getDefaultVoicePhoneId());
+            if (mgr != null) {
+                return mgr.isVtProvisioned();
             }
         }
 
-        return isProvisioned;
+        return true;
     }
 
     /**
@@ -284,7 +343,65 @@ public class ImsManager {
                 context.getResources().getBoolean(
                         com.android.internal.R.bool.config_device_vt_available) &&
                 getBooleanCarrierConfig(context,
-                        CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL);
+                        CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
+                isGbaValid(context);
+    }
+
+    /**
+     * Returns the user configuration of VT setting
+     */
+    public static boolean isVtEnabledByUser(Context context) {
+        int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
+                android.provider.Settings.Global.VT_IMS_ENABLED,
+                ImsConfig.FeatureValueConstants.ON);
+        return (enabled == 1) ? true : false;
+    }
+
+    /**
+     * Change persistent VT enabled setting
+     */
+    public static void setVtSetting(Context context, boolean enabled) {
+        int value = enabled ? 1 : 0;
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.VT_IMS_ENABLED, value);
+
+        ImsManager imsManager = ImsManager.getInstance(context,
+                SubscriptionManager.getDefaultVoicePhoneId());
+        if (imsManager != null) {
+            try {
+                ImsConfig config = imsManager.getConfigInterface();
+                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        enabled ? ImsConfig.FeatureValueConstants.ON
+                                : ImsConfig.FeatureValueConstants.OFF,
+                        imsManager.mImsConfigListener);
+
+                if (enabled) {
+                    log("setVtSetting() : turnOnIms");
+                    imsManager.turnOnIms();
+                } else if (isTurnOffImsAllowedByPlatform(context)
+                        && (!isVolteEnabledByPlatform(context)
+                        || !isEnhanced4gLteModeSettingEnabledByUser(context))) {
+                    log("setVtSetting() : imsServiceAllowTurnOff -> turnOffIms");
+                    imsManager.turnOffIms();
+                }
+            } catch (ImsException e) {
+                loge("setVtSetting(): ", e);
+            }
+        }
+    }
+
+    /*
+     * Returns whether turning off ims is allowed by platform.
+     * The platform property may override the carrier config.
+     */
+    private static boolean isTurnOffImsAllowedByPlatform(Context context) {
+        if (SystemProperties.getInt(PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE,
+                PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE_DEFAULT) == 1) {
+            return true;
+        }
+        return getBooleanCarrierConfig(context,
+                CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL);
     }
 
     /**
@@ -293,7 +410,9 @@ public class ImsManager {
     public static boolean isWfcEnabledByUser(Context context) {
         int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
                 android.provider.Settings.Global.WFC_IMS_ENABLED,
-                ImsConfig.FeatureValueConstants.OFF);
+                getBooleanCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
+                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
         return (enabled == 1) ? true : false;
     }
 
@@ -310,17 +429,16 @@ public class ImsManager {
         if (imsManager != null) {
             try {
                 ImsConfig config = imsManager.getConfigInterface();
-                // FIXME: replace NETWORK_TYPE_LTE with NETWORK_TYPE_IWLAN
-                // when available
                 config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI,
-                        TelephonyManager.NETWORK_TYPE_LTE,
+                        TelephonyManager.NETWORK_TYPE_IWLAN,
                         enabled ? ImsConfig.FeatureValueConstants.ON
-                                : ImsConfig.FeatureValueConstants.OFF, null);
+                                : ImsConfig.FeatureValueConstants.OFF,
+                        imsManager.mImsConfigListener);
 
                 if (enabled) {
+                    log("setWfcSetting() : turnOnIms");
                     imsManager.turnOnIms();
-                } else if (getBooleanCarrierConfig(context,
-                        CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL)
+                } else if (isTurnOffImsAllowedByPlatform(context)
                         && (!isVolteEnabledByPlatform(context)
                         || !isEnhanced4gLteModeSettingEnabledByUser(context))) {
                     log("setWfcSetting() : imsServiceAllowTurnOff -> turnOffIms");
@@ -332,24 +450,24 @@ public class ImsManager {
                         ? getWfcMode(context)
                         : ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED);
             } catch (ImsException e) {
-                loge("setWfcSetting(): " + e);
+                loge("setWfcSetting(): ", e);
             }
         }
     }
 
     /**
-     * Returns the user configuration of WFC modem setting
+     * Returns the user configuration of WFC preference setting
      */
     public static int getWfcMode(Context context) {
         int setting = android.provider.Settings.Global.getInt(context.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_MODE,
-                ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED);
+                android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
         if (DBG) log("getWfcMode - setting=" + setting);
         return setting;
     }
 
     /**
-     * Returns the user configuration of WFC modem setting
+     * Change persistent WFC preference setting
      */
     public static void setWfcMode(Context context, int wfcMode) {
         if (DBG) log("setWfcMode - setting=" + wfcMode);
@@ -359,12 +477,57 @@ public class ImsManager {
         setWfcModeInternal(context, wfcMode);
     }
 
+    /**
+     * Returns the user configuration of WFC preference setting
+     *
+     * @param roaming {@code false} for home network setting, {@code true} for roaming  setting
+     */
+    public static int getWfcMode(Context context, boolean roaming) {
+        int setting = 0;
+        if (!roaming) {
+            setting = android.provider.Settings.Global.getInt(context.getContentResolver(),
+                    android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(context,
+                            CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+            if (DBG) log("getWfcMode - setting=" + setting);
+        } else {
+            setting = android.provider.Settings.Global.getInt(context.getContentResolver(),
+                    android.provider.Settings.Global.WFC_IMS_ROAMING_MODE,
+                    getIntCarrierConfig(context,
+                            CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT));
+            if (DBG) log("getWfcMode (roaming) - setting=" + setting);
+        }
+        return setting;
+    }
+
+    /**
+     * Change persistent WFC preference setting
+     *
+     * @param roaming {@code false} for home network setting, {@code true} for roaming setting
+     */
+    public static void setWfcMode(Context context, int wfcMode, boolean roaming) {
+        if (!roaming) {
+            if (DBG) log("setWfcMode - setting=" + wfcMode);
+            android.provider.Settings.Global.putInt(context.getContentResolver(),
+                    android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
+        } else {
+            if (DBG) log("setWfcMode (roaming) - setting=" + wfcMode);
+            android.provider.Settings.Global.putInt(context.getContentResolver(),
+                    android.provider.Settings.Global.WFC_IMS_ROAMING_MODE, wfcMode);
+        }
+
+        TelephonyManager tm = (TelephonyManager)
+                context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (roaming == tm.isNetworkRoaming()) {
+            setWfcModeInternal(context, wfcMode);
+        }
+    }
+
     private static void setWfcModeInternal(Context context, int wfcMode) {
         final ImsManager imsManager = ImsManager.getInstance(context,
                 SubscriptionManager.getDefaultVoicePhoneId());
         if (imsManager != null) {
             final int value = wfcMode;
-            QueuedWork.singleThreadExecutor().submit(new Runnable() {
+            Thread thread = new Thread(new Runnable() {
                 public void run() {
                     try {
                         imsManager.getConfigInterface().setProvisionedValue(
@@ -375,6 +538,7 @@ public class ImsManager {
                     }
                 }
             });
+            thread.start();
         }
     }
 
@@ -384,7 +548,9 @@ public class ImsManager {
     public static boolean isWfcRoamingEnabledByUser(Context context) {
         int enabled = android.provider.Settings.Global.getInt(context.getContentResolver(),
                 android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                ImsConfig.FeatureValueConstants.OFF);
+                getBooleanCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
+                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
         return (enabled == 1) ? true : false;
     }
 
@@ -392,16 +558,22 @@ public class ImsManager {
      * Change persistent WFC roaming enabled setting
      */
     public static void setWfcRoamingSetting(Context context, boolean enabled) {
-        final int value = enabled
-                ? ImsConfig.FeatureValueConstants.ON
-                : ImsConfig.FeatureValueConstants.OFF;
         android.provider.Settings.Global.putInt(context.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED, value);
+                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
+                enabled ? ImsConfig.FeatureValueConstants.ON
+                        : ImsConfig.FeatureValueConstants.OFF);
+
+        setWfcRoamingSettingInternal(context, enabled);
+    }
 
+    private static void setWfcRoamingSettingInternal(Context context, boolean enabled) {
         final ImsManager imsManager = ImsManager.getInstance(context,
                 SubscriptionManager.getDefaultVoicePhoneId());
         if (imsManager != null) {
-            QueuedWork.singleThreadExecutor().submit(new Runnable() {
+            final int value = enabled
+                    ? ImsConfig.FeatureValueConstants.ON
+                    : ImsConfig.FeatureValueConstants.OFF;
+            Thread thread = new Thread(new Runnable() {
                 public void run() {
                     try {
                         imsManager.getConfigInterface().setProvisionedValue(
@@ -412,6 +584,7 @@ public class ImsManager {
                     }
                 }
             });
+            thread.start();
         }
     }
 
@@ -430,7 +603,254 @@ public class ImsManager {
                context.getResources().getBoolean(
                        com.android.internal.R.bool.config_device_wfc_ims_available) &&
                getBooleanCarrierConfig(context,
-                       CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL);
+                       CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL) &&
+               isGbaValid(context);
+    }
+
+    /**
+     * If carrier requires that IMS is only available if GBA capable SIM is used,
+     * then this function checks GBA bit in EF IST.
+     *
+     * Format of EF IST is defined in 3GPP TS 31.103 (Section 4.2.7).
+     */
+    private static boolean isGbaValid(Context context) {
+        if (getBooleanCarrierConfig(context,
+                CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL)) {
+            final TelephonyManager telephonyManager = TelephonyManager.getDefault();
+            String efIst = telephonyManager.getIsimIst();
+            if (efIst == null) {
+                loge("ISF is NULL");
+                return true;
+            }
+            boolean result = efIst != null && efIst.length() > 1 &&
+                    (0x02 & (byte)efIst.charAt(1)) != 0;
+            if (DBG) log("GBA capable=" + result + ", ISF=" + efIst);
+            return result;
+        }
+        return true;
+    }
+
+    /**
+     * This function should be called when ImsConfig.ACTION_IMS_CONFIG_CHANGED is received.
+     *
+     * We cannot register receiver in ImsManager because this would lead to resource leak.
+     * ImsManager can be created in different processes and it is not notified when that process
+     * is about to be terminated.
+     *
+     * @hide
+     * */
+    public static void onProvisionedValueChanged(Context context, int item, String value) {
+        if (DBG) Rlog.d(TAG, "onProvisionedValueChanged: item=" + item + " val=" + value);
+        ImsManager mgr = ImsManager.getInstance(context,
+                SubscriptionManager.getDefaultVoicePhoneId());
+
+        switch (item) {
+            case ImsConfig.ConfigConstants.VLT_SETTING_ENABLED:
+                mgr.setVolteProvisionedProperty(value.equals("1"));
+                if (DBG) Rlog.d(TAG,"isVoLteProvisioned = " + mgr.isVolteProvisioned());
+                break;
+
+            case ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED:
+                mgr.setWfcProvisionedProperty(value.equals("1"));
+                if (DBG) Rlog.d(TAG,"isWfcProvisioned = " + mgr.isWfcProvisioned());
+                break;
+
+            case ImsConfig.ConfigConstants.LVC_SETTING_ENABLED:
+                mgr.setVtProvisionedProperty(value.equals("1"));
+                if (DBG) Rlog.d(TAG,"isVtProvisioned = " + mgr.isVtProvisioned());
+                break;
+
+        }
+    }
+
+    private class AsyncUpdateProvisionedValues extends AsyncTask<Void, Void, Void> {
+        @Override
+        protected Void doInBackground(Void... params) {
+            // disable on any error
+            setVolteProvisionedProperty(false);
+            setWfcProvisionedProperty(false);
+            setVtProvisionedProperty(false);
+
+            try {
+                ImsConfig config = getConfigInterface();
+                if (config != null) {
+                    setVolteProvisionedProperty(getProvisionedBool(config,
+                            ImsConfig.ConfigConstants.VLT_SETTING_ENABLED));
+                    if (DBG) Rlog.d(TAG, "isVoLteProvisioned = " + isVolteProvisioned());
+
+                    setWfcProvisionedProperty(getProvisionedBool(config,
+                            ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED));
+                    if (DBG) Rlog.d(TAG, "isWfcProvisioned = " + isWfcProvisioned());
+
+                    setVtProvisionedProperty(getProvisionedBool(config,
+                            ImsConfig.ConfigConstants.LVC_SETTING_ENABLED));
+                    if (DBG) Rlog.d(TAG, "isVtProvisioned = " + isVtProvisioned());
+
+                }
+            } catch (ImsException ie) {
+                Rlog.e(TAG, "AsyncUpdateProvisionedValues error: ", ie);
+            }
+
+            return null;
+        }
+
+        private boolean getProvisionedBool(ImsConfig config, int item) throws ImsException {
+            return config.getProvisionedValue(item) == ImsConfig.FeatureValueConstants.ON;
+        }
+    }
+
+    /** Asynchronously get VoLTE, WFC, VT provisioning statuses */
+    private void updateProvisionedValues() {
+        if (getBooleanCarrierConfig(mContext,
+                CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
+
+            new AsyncUpdateProvisionedValues().execute();
+        }
+    }
+
+    /**
+     * Sync carrier config and user settings with ImsConfig.
+     *
+     * @param context for the manager object
+     * @param phoneId phone id
+     * @param force update
+     */
+    public static void updateImsServiceConfig(Context context, int phoneId, boolean force) {
+        if (!force) {
+            if (TelephonyManager.getDefault().getSimState() != TelephonyManager.SIM_STATE_READY) {
+                log("updateImsServiceConfig: SIM not ready");
+                // Don't disable IMS if SIM is not ready
+                return;
+            }
+        }
+
+        final ImsManager imsManager = ImsManager.getInstance(context, phoneId);
+        if (imsManager != null && (!imsManager.mConfigUpdated || force)) {
+            try {
+                imsManager.updateProvisionedValues();
+
+                // TODO: Extend ImsConfig API and set all feature values in single function call.
+
+                // Note: currently the order of updates is set to produce different order of
+                // setFeatureValue() function calls from setAdvanced4GMode(). This is done to
+                // differentiate this code path from vendor code perspective.
+                boolean isImsUsed = imsManager.updateVolteFeatureValue();
+                isImsUsed |= imsManager.updateWfcFeatureAndProvisionedValues();
+                isImsUsed |= imsManager.updateVideoCallFeatureValue();
+
+                if (isImsUsed || !isTurnOffImsAllowedByPlatform(context)) {
+                    // Turn on IMS if it is used.
+                    // Also, if turning off is not allowed for current carrier,
+                    // we need to turn IMS on because it might be turned off before
+                    // phone switched to current carrier.
+                    log("updateImsServiceConfig: turnOnIms");
+                    imsManager.turnOnIms();
+                } else {
+                    // Turn off IMS if it is not used AND turning off is allowed for carrier.
+                    log("updateImsServiceConfig: turnOffIms");
+                    imsManager.turnOffIms();
+                }
+
+                imsManager.mConfigUpdated = true;
+            } catch (ImsException e) {
+                loge("updateImsServiceConfig: ", e);
+                imsManager.mConfigUpdated = false;
+            }
+        }
+    }
+
+    /**
+     * Update VoLTE config
+     * @return whether feature is On
+     * @throws ImsException
+     */
+    private boolean updateVolteFeatureValue() throws ImsException {
+        boolean available = isVolteEnabledByPlatform(mContext);
+        boolean enabled = isEnhanced4gLteModeSettingEnabledByUser(mContext);
+        boolean isNonTty = isNonTtyOrTtyOnVolteEnabled(mContext);
+        boolean isFeatureOn = available && enabled && isNonTty;
+
+        log("updateVolteFeatureValue: available = " + available
+                + ", enabled = " + enabled
+                + ", nonTTY = " + isNonTty);
+
+        getConfigInterface().setFeatureValue(
+                ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                isFeatureOn ?
+                        ImsConfig.FeatureValueConstants.ON :
+                        ImsConfig.FeatureValueConstants.OFF,
+                mImsConfigListener);
+
+        return isFeatureOn;
+    }
+
+    /**
+     * Update video call over LTE config
+     * @return whether feature is On
+     * @throws ImsException
+     */
+    private boolean updateVideoCallFeatureValue() throws ImsException {
+        boolean available = isVtEnabledByPlatform(mContext);
+        boolean enabled = isVtEnabledByUser(mContext);
+        boolean isNonTty = isNonTtyOrTtyOnVolteEnabled(mContext);
+        boolean isDataEnabled = isDataEnabled();
+        boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(mContext,
+                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
+
+        boolean isFeatureOn = available && enabled && isNonTty
+                && (ignoreDataEnabledChanged || isDataEnabled);
+
+        log("updateVideoCallFeatureValue: available = " + available
+                + ", enabled = " + enabled
+                + ", nonTTY = " + isNonTty
+                + ", data enabled = " + isDataEnabled);
+
+        getConfigInterface().setFeatureValue(
+                ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                isFeatureOn ?
+                        ImsConfig.FeatureValueConstants.ON :
+                        ImsConfig.FeatureValueConstants.OFF,
+                mImsConfigListener);
+
+        return isFeatureOn;
+    }
+
+    /**
+     * Update WFC config
+     * @return whether feature is On
+     * @throws ImsException
+     */
+    private boolean updateWfcFeatureAndProvisionedValues() throws ImsException {
+        boolean isNetworkRoaming = TelephonyManager.getDefault().isNetworkRoaming();
+        boolean available = isWfcEnabledByPlatform(mContext);
+        boolean enabled = isWfcEnabledByUser(mContext);
+        int mode = getWfcMode(mContext, isNetworkRoaming);
+        boolean roaming = isWfcRoamingEnabledByUser(mContext);
+        boolean isFeatureOn = available && enabled;
+
+        log("updateWfcFeatureAndProvisionedValues: available = " + available
+                + ", enabled = " + enabled
+                + ", mode = " + mode
+                + ", roaming = " + roaming);
+
+        getConfigInterface().setFeatureValue(
+                ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI,
+                TelephonyManager.NETWORK_TYPE_IWLAN,
+                isFeatureOn ?
+                        ImsConfig.FeatureValueConstants.ON :
+                        ImsConfig.FeatureValueConstants.OFF,
+                mImsConfigListener);
+
+        if (!isFeatureOn) {
+            mode = ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
+            roaming = false;
+        }
+        setWfcModeInternal(mContext, mode);
+        setWfcRoamingSettingInternal(mContext, roaming);
+
+        return isFeatureOn;
     }
 
     private ImsManager(Context context, int phoneId) {
@@ -455,6 +875,10 @@ public class ImsManager {
         return false;
     }
 
+    public void setImsConfigListener(ImsConfigListener listener) {
+        mImsConfigListener = listener;
+    }
+
     /**
      * Opens the IMS service for making calls and/or receiving generic IMS calls.
      * The caller may make subsquent calls through {@link #makeCall}.
@@ -511,6 +935,32 @@ public class ImsManager {
     }
 
     /**
+     * Adds registration listener to the IMS service.
+     *
+     * @param serviceClass a service class specified in {@link ImsServiceClass}
+     *      For VoLTE service, it MUST be a {@link ImsServiceClass#MMTEL}.
+     * @param listener To listen to IMS registration events; It cannot be null
+     * @throws NullPointerException if {@code listener} is null
+     * @throws ImsException if calling the IMS service results in an error
+     */
+    public void addRegistrationListener(int serviceClass, ImsConnectionStateListener listener)
+            throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        if (listener == null) {
+            throw new NullPointerException("listener can't be null");
+        }
+
+        try {
+            mImsService.addRegistrationListener(mPhoneId, serviceClass,
+                    createRegistrationListenerProxy(serviceClass, listener));
+        } catch (RemoteException e) {
+            throw new ImsException("addRegistrationListener()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
      * Closes the specified service ({@link ImsServiceClass}) not to make/receive calls.
      * All the resources that were allocated to the service are also released.
      *
@@ -529,6 +979,7 @@ public class ImsManager {
             mUt = null;
             mConfig = null;
             mEcbm = null;
+            mMultiEndpoint = null;
         }
     }
 
@@ -657,7 +1108,7 @@ public class ImsManager {
             ImsCall.Listener listener) throws ImsException {
         if (DBG) {
             log("makeCall :: serviceId=" + serviceId
-                    + ", profile=" + profile + ", callees=" + callees);
+                    + ", profile=" + profile);
         }
 
         checkAndThrowExceptionIfServiceUnavailable();
@@ -788,8 +1239,12 @@ public class ImsManager {
     private static boolean getBooleanCarrierConfig(Context context, String key) {
         CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(
                 Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle b = null;
         if (configManager != null) {
-            return configManager.getConfig().getBoolean(key);
+            b = configManager.getConfig();
+        }
+        if (b != null) {
+            return b.getBoolean(key);
         } else {
             // Return static default defined in CarrierConfigManager.
             return CarrierConfigManager.getDefaultConfig().getBoolean(key);
@@ -797,6 +1252,28 @@ public class ImsManager {
     }
 
     /**
+     * Get the int config from carrier config manager.
+     *
+     * @param context the context to get carrier service
+     * @param key config key defined in CarrierConfigManager
+     * @return integer value of corresponding key.
+     */
+    private static int getIntCarrierConfig(Context context, String key) {
+        CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle b = null;
+        if (configManager != null) {
+            b = configManager.getConfig();
+        }
+        if (b != null) {
+            return b.getInt(key);
+        } else {
+            // Return static default defined in CarrierConfigManager.
+            return CarrierConfigManager.getDefaultConfig().getInt(key);
+        }
+    }
+
+    /**
      * Gets the call ID from the specified incoming call broadcast intent.
      *
      * @param incomingCallIntent the incoming call broadcast intent
@@ -917,29 +1394,52 @@ public class ImsManager {
         }
     }
 
-    private void setAdvanced4GMode(boolean turnOn) throws ImsException {
-        checkAndThrowExceptionIfServiceUnavailable();
+    private boolean isImsTurnOffAllowed() {
+        return isTurnOffImsAllowedByPlatform(mContext)
+                && (!isWfcEnabledByPlatform(mContext)
+                || !isWfcEnabledByUser(mContext));
+    }
 
-        ImsConfig config = getConfigInterface();
-        if (config != null) {
-            config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
-                    TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
-            if (isVtEnabledByPlatform(mContext)) {
-                // TODO: once VT is available on platform replace the '1' with the current
-                // user configuration of VT.
-                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
-                        TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, null);
+    private void setLteFeatureValues(boolean turnOn) {
+        log("setLteFeatureValues: " + turnOn);
+        try {
+            ImsConfig config = getConfigInterface();
+            if (config != null) {
+                config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE,
+                        TelephonyManager.NETWORK_TYPE_LTE, turnOn ? 1 : 0, mImsConfigListener);
+
+                if (isVtEnabledByPlatform(mContext)) {
+                    boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(mContext,
+                            CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
+                    boolean enableViLte = turnOn && isVtEnabledByUser(mContext) &&
+                            (ignoreDataEnabledChanged || isDataEnabled());
+                    config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
+                            TelephonyManager.NETWORK_TYPE_LTE,
+                            enableViLte ? 1 : 0,
+                            mImsConfigListener);
+                }
             }
+        } catch (ImsException e) {
+            loge("setLteFeatureValues: exception ", e);
         }
+    }
 
+    private void setAdvanced4GMode(boolean turnOn) throws ImsException {
+        checkAndThrowExceptionIfServiceUnavailable();
+
+        // if turnOn: first set feature values then call turnOnIms()
+        // if turnOff: only set feature values if IMS turn off is not allowed. If turn off is
+        // allowed, first call turnOffIms() then set feature values
         if (turnOn) {
+            setLteFeatureValues(turnOn);
+            log("setAdvanced4GMode: turnOnIms");
             turnOnIms();
-        } else if (getBooleanCarrierConfig(mContext,
-                CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL)
-                && (!isWfcEnabledByPlatform(mContext)
-                || !isWfcEnabledByUser(mContext))) {
-            log("setAdvanced4GMode() : imsServiceAllowTurnOff -> turnOffIms");
-            turnOffIms();
+        } else {
+            if (isImsTurnOffAllowed()) {
+                log("setAdvanced4GMode: turnOffIms");
+                turnOffIms();
+            }
+            setLteFeatureValues(turnOn);
         }
     }
 
@@ -967,6 +1467,7 @@ public class ImsManager {
             mUt = null;
             mConfig = null;
             mEcbm = null;
+            mMultiEndpoint = null;
 
             if (mContext != null) {
                 Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN);
@@ -993,7 +1494,7 @@ public class ImsManager {
             return (mServiceClass == serviceClass);
         }
 
-        @Override
+        @Deprecated
         public void registrationConnected() {
             if (DBG) {
                 log("registrationConnected ::");
@@ -1004,7 +1505,7 @@ public class ImsManager {
             }
         }
 
-        @Override
+        @Deprecated
         public void registrationProgressing() {
             if (DBG) {
                 log("registrationProgressing ::");
@@ -1016,6 +1517,32 @@ public class ImsManager {
         }
 
         @Override
+        public void registrationConnectedWithRadioTech(int imsRadioTech) {
+            // Note: imsRadioTech value maps to RIL_RADIO_TECHNOLOGY
+            //       values in ServiceState.java.
+            if (DBG) {
+                log("registrationConnectedWithRadioTech :: imsRadioTech=" + imsRadioTech);
+            }
+
+            if (mListener != null) {
+                mListener.onImsConnected();
+            }
+        }
+
+        @Override
+        public void registrationProgressingWithRadioTech(int imsRadioTech) {
+            // Note: imsRadioTech value maps to RIL_RADIO_TECHNOLOGY
+            //       values in ServiceState.java.
+            if (DBG) {
+                log("registrationProgressingWithRadioTech :: imsRadioTech=" + imsRadioTech);
+            }
+
+            if (mListener != null) {
+                mListener.onImsProgressing();
+            }
+        }
+
+        @Override
         public void registrationDisconnected(ImsReasonInfo imsReasonInfo) {
             if (DBG) {
                 log("registrationDisconnected :: imsReasonInfo" + imsReasonInfo);
@@ -1069,7 +1596,25 @@ public class ImsManager {
             }
         }
 
+        @Override
+        public void voiceMessageCountUpdate(int count) {
+            log("voiceMessageCountUpdate :: count=" + count);
+
+            if (mListener != null) {
+                mListener.onVoiceMessageCountChanged(count);
+            }
+        }
+
+        @Override
+        public void registrationAssociatedUriChanged(Uri[] uris) {
+            if (DBG) log("registrationAssociatedUriChanged ::");
+
+            if (mListener != null) {
+                mListener.registrationAssociatedUriChanged(uris);
+            }
+        }
     }
+
     /**
      * Gets the ECBM interface to request ECBM exit.
      *
@@ -1096,4 +1641,140 @@ public class ImsManager {
         }
         return mEcbm;
     }
+
+    /**
+     * Gets the Multi-Endpoint interface to subscribe to multi-enpoint notifications..
+     *
+     * @param serviceId a service id which is obtained from {@link ImsManager#open}
+     * @return the multi-endpoint interface instance
+     * @throws ImsException if getting the multi-endpoint interface results in an error
+     */
+    public ImsMultiEndpoint getMultiEndpointInterface(int serviceId) throws ImsException {
+        if (mMultiEndpoint == null) {
+            checkAndThrowExceptionIfServiceUnavailable();
+
+            try {
+                IImsMultiEndpoint iImsMultiEndpoint = mImsService.getMultiEndpointInterface(
+                        serviceId);
+
+                if (iImsMultiEndpoint == null) {
+                    throw new ImsException("getMultiEndpointInterface()",
+                            ImsReasonInfo.CODE_MULTIENDPOINT_NOT_SUPPORTED);
+                }
+                mMultiEndpoint = new ImsMultiEndpoint(iImsMultiEndpoint);
+            } catch (RemoteException e) {
+                throw new ImsException("getMultiEndpointInterface()", e,
+                        ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+            }
+        }
+        return mMultiEndpoint;
+    }
+
+    /**
+     * Resets ImsManager settings back to factory defaults.
+     *
+     * @hide
+     */
+    public static void factoryReset(Context context) {
+        // Set VoLTE to default
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                ImsConfig.FeatureValueConstants.ON);
+
+        // Set VoWiFi to default
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.WFC_IMS_ENABLED,
+                getBooleanCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
+                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+
+        // Set VoWiFi mode to default
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.WFC_IMS_MODE,
+                getIntCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+
+        // Set VoWiFi roaming to default
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
+                getBooleanCarrierConfig(context,
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
+                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+
+        // Set VT to default
+        android.provider.Settings.Global.putInt(context.getContentResolver(),
+                android.provider.Settings.Global.VT_IMS_ENABLED,
+                ImsConfig.FeatureValueConstants.ON);
+
+        // Push settings to ImsConfig
+        ImsManager.updateImsServiceConfig(context,
+                SubscriptionManager.getDefaultVoicePhoneId(), true);
+    }
+
+    private boolean isDataEnabled() {
+        return SystemProperties.getBoolean(DATA_ENABLED_PROP, true);
+    }
+
+    /**
+     * Set data enabled/disabled flag.
+     * @param enabled True if data is enabled, otherwise disabled.
+     */
+    public void setDataEnabled(boolean enabled) {
+        log("setDataEnabled: " + enabled);
+        SystemProperties.set(DATA_ENABLED_PROP, enabled ? TRUE : FALSE);
+    }
+
+    private boolean isVolteProvisioned() {
+        return SystemProperties.getBoolean(VOLTE_PROVISIONED_PROP, true);
+    }
+
+    private void setVolteProvisionedProperty(boolean provisioned) {
+        SystemProperties.set(VOLTE_PROVISIONED_PROP, provisioned ? TRUE : FALSE);
+    }
+
+    private boolean isWfcProvisioned() {
+        return SystemProperties.getBoolean(WFC_PROVISIONED_PROP, true);
+    }
+
+    private void setWfcProvisionedProperty(boolean provisioned) {
+        SystemProperties.set(WFC_PROVISIONED_PROP, provisioned ? TRUE : FALSE);
+    }
+
+    private boolean isVtProvisioned() {
+        return SystemProperties.getBoolean(VT_PROVISIONED_PROP, true);
+    }
+
+    private void setVtProvisionedProperty(boolean provisioned) {
+        SystemProperties.set(VT_PROVISIONED_PROP, provisioned ? TRUE : FALSE);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("ImsManager:");
+        pw.println("  mPhoneId = " + mPhoneId);
+        pw.println("  mConfigUpdated = " + mConfigUpdated);
+        pw.println("  mImsService = " + mImsService);
+        pw.println("  mDataEnabled = " + isDataEnabled());
+        pw.println("  ignoreDataEnabledChanged = " + getBooleanCarrierConfig(mContext,
+                CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS));
+
+        pw.println("  isGbaValid = " + isGbaValid(mContext));
+        pw.println("  isImsTurnOffAllowed = " + isImsTurnOffAllowed());
+        pw.println("  isNonTtyOrTtyOnVolteEnabled = " + isNonTtyOrTtyOnVolteEnabled(mContext));
+
+        pw.println("  isVolteEnabledByPlatform = " + isVolteEnabledByPlatform(mContext));
+        pw.println("  isVolteProvisionedOnDevice = " + isVolteProvisionedOnDevice(mContext));
+        pw.println("  isEnhanced4gLteModeSettingEnabledByUser = " +
+                isEnhanced4gLteModeSettingEnabledByUser(mContext));
+        pw.println("  isVtEnabledByPlatform = " + isVtEnabledByPlatform(mContext));
+        pw.println("  isVtEnabledByUser = " + isVtEnabledByUser(mContext));
+
+        pw.println("  isWfcEnabledByPlatform = " + isWfcEnabledByPlatform(mContext));
+        pw.println("  isWfcEnabledByUser = " + isWfcEnabledByUser(mContext));
+        pw.println("  getWfcMode = " + getWfcMode(mContext));
+        pw.println("  isWfcRoamingEnabledByUser = " + isWfcRoamingEnabledByUser(mContext));
+
+        pw.println("  isVtProvisionedOnDevice = " + isVtProvisionedOnDevice(mContext));
+        pw.println("  isWfcProvisionedOnDevice = " + isWfcProvisionedOnDevice(mContext));
+        pw.flush();
+    }
 }