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.Parcel;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
+import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsServiceProxy;
+import android.telephony.ims.ImsServiceProxyCompat;
+import android.telephony.ims.feature.ImsFeature;
import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsConfig;
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.IImsServiceController;
import com.android.ims.internal.IImsUt;
import com.android.ims.internal.ImsCallSession;
import com.android.ims.internal.IImsConfig;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
/**
* Provides APIs for IMS services, such as initiating IMS calls, and provides access to
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.
/**
* Action to broadcast when ImsService is up.
* Internal use only.
+ * @deprecated
* @hide
*/
public static final String ACTION_IMS_SERVICE_UP =
/**
* Action to broadcast when ImsService is down.
* Internal use only.
+ * @deprecated
* @hide
*/
public static final String ACTION_IMS_SERVICE_DOWN =
*/
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;
private Context mContext;
private int mPhoneId;
- private IImsService mImsService = null;
+ private final boolean mConfigDynamicBind;
+ private ImsServiceProxyCompat mImsServiceProxy = null;
private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
// Ut interface for the supplementary service configuration
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;
+
+ private Set<ImsServiceProxy.INotifyStatusChanged> mStatusCallbacks = new HashSet<>();
+
+ // Keep track of the ImsRegistrationListenerProxys that have been created so that we can
+ // remove them from the ImsService.
+ private Set<ImsRegistrationListenerProxy> mRegistrationListeners = new HashSet<>();
+
+ // 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";
+
+ // mRecentDisconnectReasons stores the last 16 disconnect reasons
+ private static final int MAX_RECENT_DISCONNECT_REASONS = 16;
+ private ConcurrentLinkedDeque<ImsReasonInfo> mRecentDisconnectReasons =
+ new ConcurrentLinkedDeque<>();
+
/**
* Gets a manager instance.
*
*/
public static ImsManager getInstance(Context context, int phoneId) {
synchronized (sImsManagerInstances) {
- if (sImsManagerInstances.containsKey(phoneId))
+ if (sImsManagerInstances.containsKey(phoneId)) {
return sImsManagerInstances.get(phoneId);
+ }
ImsManager mgr = new ImsManager(context, phoneId);
sImsManagerInstances.put(phoneId, mgr);
* 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);
* supported.
*/
public static boolean isNonTtyOrTtyOnVolteEnabled(Context context) {
- if (context.getResources().getBoolean(
- com.android.internal.R.bool.config_carrier_volte_tty_supported)) {
+ if (getBooleanCarrierConfig(context,
+ CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
return true;
}
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) && context.getResources()
- .getBoolean(com.android.internal.R.bool.config_carrier_volte_available)
- && !disabledByGlobalSetting;
+ com.android.internal.R.bool.config_device_volte_available)
+ && getBooleanCarrierConfig(context,
+ CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
+ && isGbaValid(context);
}
- /*
+ /**
* Indicates whether VoLTE is provisioned on device
*/
public static boolean isVolteProvisionedOnDevice(Context context) {
- boolean isProvisioned = true;
- if (context.getResources().getBoolean(
- com.android.internal.R.bool.config_carrier_volte_provisioned)) {
- isProvisioned = false; // disable on any error
+ if (getBooleanCarrierConfig(context,
+ CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
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 isProvisioned;
+ 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 true;
}
/**
return
context.getResources().getBoolean(
com.android.internal.R.bool.config_device_vt_available) &&
- context.getResources().getBoolean(
- com.android.internal.R.bool.config_carrier_vt_available);
+ getBooleanCarrierConfig(context,
+ 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);
}
/**
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;
}
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 (context.getResources().getBoolean(
- com.android.internal.R.bool.imsServiceAllowTurnOff)
+ } else if (isTurnOffImsAllowedByPlatform(context)
&& (!isVolteEnabledByPlatform(context)
|| !isEnhanced4gLteModeSettingEnabledByUser(context))) {
log("setWfcSetting() : imsServiceAllowTurnOff -> turnOffIms");
imsManager.turnOffIms();
}
- // Force IMS to register over LTE when turning off WFC
+ TelephonyManager tm = (TelephonyManager) context
+ .getSystemService(Context.TELEPHONY_SERVICE);
setWfcModeInternal(context, enabled
- ? getWfcMode(context)
+ // Choose wfc mode per current roaming preference
+ ? getWfcMode(context, tm.isNetworkRoaming())
+ // Force IMS to register over LTE when turning off WFC
: 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);
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(
}
}
});
+ thread.start();
}
}
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;
}
* 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(
}
}
});
+ thread.start();
}
}
return
context.getResources().getBoolean(
com.android.internal.R.bool.config_device_wfc_ims_available) &&
- context.getResources().getBoolean(
- com.android.internal.R.bool.config_carrier_wfc_ims_available);
+ getBooleanCarrierConfig(context,
+ 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 ImsManager(Context context, int phoneId) {
+ 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;
+ }
+
+ /**
+ * Do NOT use this directly, instead use {@link #getInstance}.
+ */
+ @VisibleForTesting
+ public ImsManager(Context context, int phoneId) {
mContext = context;
mPhoneId = phoneId;
- createImsService(true);
+ mConfigDynamicBind = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dynamic_bind_ims);
+ addNotifyStatusChangedCallback(this::sendImsServiceIntent);
+ createImsService();
+ }
+
+ /**
+ * Provide backwards compatibility using deprecated service UP/DOWN intents.
+ */
+ private void sendImsServiceIntent() {
+ int status = mImsServiceProxy.getFeatureStatus();
+ Intent intent;
+ switch (status) {
+ case ImsFeature.STATE_NOT_AVAILABLE:
+ case ImsFeature.STATE_INITIALIZING:
+ intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+ break;
+ case ImsFeature.STATE_READY:
+ intent = new Intent(ACTION_IMS_SERVICE_UP);
+ break;
+ default:
+ intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+ }
+ intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
+ mContext.sendBroadcast(new Intent(intent));
+ }
+
+
+ /**
+ * @return Whether or not ImsManager is configured to Dynamically bind or not to support legacy
+ * devices.
+ */
+ public boolean isDynamicBinding() {
+ return mConfigDynamicBind;
}
/*
* Returns a flag indicating whether the IMS service is available.
*/
public boolean isServiceAvailable() {
- if (mImsService != null) {
- return true;
+ if (mImsServiceProxy == null) {
+ createImsService();
}
+ // mImsServiceProxy will always create an ImsServiceProxy.
+ return mImsServiceProxy.isBinderAlive();
+ }
- IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId));
- if (binder != null) {
- return true;
- }
+ public void setImsConfigListener(ImsConfigListener listener) {
+ mImsConfigListener = listener;
+ }
- return false;
+ public void addNotifyStatusChangedCallback(ImsServiceProxy.INotifyStatusChanged c) {
+ if (c != null) {
+ mStatusCallbacks.add(c);
+ }
}
/**
* or {@code listener} is null
* @throws ImsException if calling the IMS service results in an error
* @see #getCallId
- * @see #getServiceId
+ * @see #getImsSessionId
*/
public int open(int serviceClass, PendingIntent incomingCallPendingIntent,
ImsConnectionStateListener listener) throws ImsException {
int result = 0;
try {
- result = mImsService.open(mPhoneId, serviceClass, incomingCallPendingIntent,
+ result = mImsServiceProxy.startSession(incomingCallPendingIntent,
createRegistrationListenerProxy(serviceClass, listener));
} catch (RemoteException e) {
throw new ImsException("open()", e,
return result;
}
+ /**
+ * 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 {
+ ImsRegistrationListenerProxy p = createRegistrationListenerProxy(serviceClass,
+ listener);
+ mRegistrationListeners.add(p);
+ mImsServiceProxy.addRegistrationListener(p);
+ } catch (RemoteException e) {
+ throw new ImsException("addRegistrationListener()", e,
+ ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+ }
+ }
+
+ /**
+ * Removes the registration listener from the IMS service.
+ *
+ * @param listener Previously registered listener that will be removed. Can not be null.
+ * @throws NullPointerException if {@code listener} is null
+ * @throws ImsException if calling the IMS service results in an error
+ * instead.
+ */
+ public void removeRegistrationListener(ImsConnectionStateListener listener)
+ throws ImsException {
+ checkAndThrowExceptionIfServiceUnavailable();
+
+ if (listener == null) {
+ throw new NullPointerException("listener can't be null");
+ }
+
+ try {
+ Optional<ImsRegistrationListenerProxy> optionalProxy = mRegistrationListeners.stream()
+ .filter(l -> listener.equals(l.mListener)).findFirst();
+ if(optionalProxy.isPresent()) {
+ ImsRegistrationListenerProxy p = optionalProxy.get();
+ mRegistrationListeners.remove(p);
+ mImsServiceProxy.removeRegistrationListener(p);
+ }
+ } catch (RemoteException e) {
+ throw new ImsException("removeRegistrationListener()", 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.
*
- * @param serviceId a service id to be closed which is obtained from {@link ImsManager#open}
+ * @param sessionId a session id to be closed which is obtained from {@link ImsManager#open}
* @throws ImsException if calling the IMS service results in an error
*/
- public void close(int serviceId) throws ImsException {
+ public void close(int sessionId) throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
- mImsService.close(serviceId);
+ mImsServiceProxy.endSession(sessionId);
} catch (RemoteException e) {
throw new ImsException("close()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
mUt = null;
mConfig = null;
mEcbm = null;
+ mMultiEndpoint = null;
}
}
/**
* Gets the configuration interface to provision / withdraw the supplementary service settings.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
* @return the Ut interface instance
* @throws ImsException if getting the Ut interface results in an error
*/
- public ImsUtInterface getSupplementaryServiceConfiguration(int serviceId)
+ public ImsUtInterface getSupplementaryServiceConfiguration()
throws ImsException {
- // FIXME: manage the multiple Ut interfaces based on the service id
- if (mUt == null) {
+ // FIXME: manage the multiple Ut interfaces based on the session id
+ if (mUt == null || !mImsServiceProxy.isBinderAlive()) {
checkAndThrowExceptionIfServiceUnavailable();
try {
- IImsUt iUt = mImsService.getUtInterface(serviceId);
+ IImsUt iUt = mImsServiceProxy.getUtInterface();
if (iUt == null) {
throw new ImsException("getSupplementaryServiceConfiguration()",
* Checks if the IMS service has successfully registered to the IMS network
* with the specified service & call type.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
* @param serviceType a service type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
* {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
* false otherwise
* @throws ImsException if calling the IMS service results in an error
*/
- public boolean isConnected(int serviceId, int serviceType, int callType)
+ public boolean isConnected(int serviceType, int callType)
throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
- return mImsService.isConnected(serviceId, serviceType, callType);
+ return mImsServiceProxy.isConnected(serviceType, callType);
} catch (RemoteException e) {
throw new ImsException("isServiceConnected()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
/**
* Checks if the specified IMS service is opend.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
* @return true if the specified service id is opened; false otherwise
* @throws ImsException if calling the IMS service results in an error
*/
- public boolean isOpened(int serviceId) throws ImsException {
+ public boolean isOpened() throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
- return mImsService.isOpened(serviceId);
+ return mImsServiceProxy.isOpened();
} catch (RemoteException e) {
throw new ImsException("isOpened()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
/**
* Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
+ * @param sessionId a session id which is obtained from {@link ImsManager#open}
* @param serviceType a service type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#SERVICE_TYPE_NONE}
* {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
* @return a {@link ImsCallProfile} object
* @throws ImsException if calling the IMS service results in an error
*/
- public ImsCallProfile createCallProfile(int serviceId,
- int serviceType, int callType) throws ImsException {
+ public ImsCallProfile createCallProfile(int sessionId, int serviceType, int callType)
+ throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
- return mImsService.createCallProfile(serviceId, serviceType, callType);
+ return mImsServiceProxy.createCallProfile(sessionId, serviceType, callType);
} catch (RemoteException e) {
throw new ImsException("createCallProfile()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
/**
* Creates a {@link ImsCall} to make a call.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
+ * @param sessionId a session id which is obtained from {@link ImsManager#open}
* @param profile a call profile to make the call
* (it contains service type, call type, media information, etc.)
* @param participants participants to invite the conference call
* @return a {@link ImsCall} object
* @throws ImsException if calling the IMS service results in an error
*/
- public ImsCall makeCall(int serviceId, ImsCallProfile profile, String[] callees,
+ public ImsCall makeCall(int sessionId, ImsCallProfile profile, String[] callees,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
- log("makeCall :: serviceId=" + serviceId
- + ", profile=" + profile + ", callees=" + callees);
+ log("makeCall :: sessionId=" + sessionId
+ + ", profile=" + profile);
}
checkAndThrowExceptionIfServiceUnavailable();
ImsCall call = new ImsCall(mContext, profile);
call.setListener(listener);
- ImsCallSession session = createCallSession(serviceId, profile);
+ ImsCallSession session = createCallSession(sessionId, profile);
if ((callees != null) && (callees.length == 1)) {
call.start(session, callees[0]);
/**
* Creates a {@link ImsCall} to take an incoming call.
*
- * @param serviceId a service id which is obtained from {@link ImsManager#open}
+ * @param sessionId a session id which is obtained from {@link ImsManager#open}
* @param incomingCallIntent the incoming call broadcast intent
* @param listener to listen to the call events from {@link ImsCall}
* @return a {@link ImsCall} object
* @throws ImsException if calling the IMS service results in an error
*/
- public ImsCall takeCall(int serviceId, Intent incomingCallIntent,
+ public ImsCall takeCall(int sessionId, Intent incomingCallIntent,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
- log("takeCall :: serviceId=" + serviceId
+ log("takeCall :: sessionId=" + sessionId
+ ", incomingCall=" + incomingCallIntent);
}
ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
}
- int incomingServiceId = getServiceId(incomingCallIntent);
+ int incomingServiceId = getImsSessionId(incomingCallIntent);
- if (serviceId != incomingServiceId) {
+ if (sessionId != incomingServiceId) {
throw new ImsException("Service id is mismatched in the incoming call intent",
ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
}
}
try {
- IImsCallSession session = mImsService.getPendingCallSession(serviceId, callId);
+ IImsCallSession session = mImsServiceProxy.getPendingCallSession(sessionId, callId);
if (session == null) {
throw new ImsException("No pending session for the call",
*/
public ImsConfig getConfigInterface() throws ImsException {
- if (mConfig == null) {
+ if (mConfig == null || !mImsServiceProxy.isBinderAlive()) {
checkAndThrowExceptionIfServiceUnavailable();
try {
- IImsConfig config = mImsService.getConfigInterface(mPhoneId);
+ IImsConfig config = mImsServiceProxy.getConfigInterface();
if (config == null) {
throw new ImsException("getConfigInterface()",
ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
return mConfig;
}
- public void setUiTTYMode(Context context, int serviceId, int uiTtyMode, Message onComplete)
+ public void setUiTTYMode(Context context, int uiTtyMode, Message onComplete)
throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
- mImsService.setUiTTYMode(serviceId, uiTtyMode, onComplete);
+ mImsServiceProxy.setUiTTYMode(uiTtyMode, onComplete);
} catch (RemoteException e) {
throw new ImsException("setTTYMode()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
- if (!context.getResources().getBoolean(
- com.android.internal.R.bool.config_carrier_volte_tty_supported)) {
+ if (!getBooleanCarrierConfig(context,
+ CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
setAdvanced4GMode((uiTtyMode == TelecomManager.TTY_MODE_OFF) &&
isEnhanced4gLteModeSettingEnabledByUser(context));
}
}
+ private ImsReasonInfo makeACopy(ImsReasonInfo imsReasonInfo) {
+ Parcel p = Parcel.obtain();
+ imsReasonInfo.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ImsReasonInfo clonedReasonInfo = ImsReasonInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ return clonedReasonInfo;
+ }
+
+ /**
+ * Get Recent IMS Disconnect Reasons.
+ *
+ * @return ArrayList of ImsReasonInfo objects. MAX size of the arraylist
+ * is MAX_RECENT_DISCONNECT_REASONS. The objects are in the
+ * chronological order.
+ */
+ public ArrayList<ImsReasonInfo> getRecentImsDisconnectReasons() {
+ ArrayList<ImsReasonInfo> disconnectReasons = new ArrayList<>();
+
+ for (ImsReasonInfo reason : mRecentDisconnectReasons) {
+ disconnectReasons.add(makeACopy(reason));
+ }
+ return disconnectReasons;
+ }
+
+ public int getImsServiceStatus() throws ImsException {
+ checkAndThrowExceptionIfServiceUnavailable();
+
+ return mImsServiceProxy.getFeatureStatus();
+ }
+
+ /**
+ * Get the boolean config from carrier config manager.
+ *
+ * @param context the context to get carrier service
+ * @param key config key defined in CarrierConfigManager
+ * @return boolean value of corresponding key.
+ */
+ private static boolean getBooleanCarrierConfig(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.getBoolean(key);
+ } else {
+ // Return static default defined in CarrierConfigManager.
+ return CarrierConfigManager.getDefaultConfig().getBoolean(key);
+ }
+ }
+
+ /**
+ * 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.
*
* Gets the service type from the specified incoming call broadcast intent.
*
* @param incomingCallIntent the incoming call broadcast intent
- * @return the service identifier or -1 if the intent does not contain it
+ * @return the session identifier or -1 if the intent does not contain it
*/
- private static int getServiceId(Intent incomingCallIntent) {
+ private static int getImsSessionId(Intent incomingCallIntent) {
if (incomingCallIntent == null) {
return (-1);
}
*/
private void checkAndThrowExceptionIfServiceUnavailable()
throws ImsException {
- if (mImsService == null) {
- createImsService(true);
+ if (mImsServiceProxy == null || !mImsServiceProxy.isBinderAlive()) {
+ createImsService();
- if (mImsService == null) {
+ if (mImsServiceProxy == null) {
throw new ImsException("Service is unavailable",
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
}
- private static String getImsServiceName(int phoneId) {
- // TODO: MSIM implementation needs to decide on service name as a function of phoneId
- return IMS_SERVICE;
- }
-
/**
- * Binds the IMS service to make/receive the call.
+ * Binds the IMS service to make/receive the call. Supports two methods of exposing an
+ * ImsService:
+ * 1) com.android.ims.ImsService implementation in ServiceManager (deprecated).
+ * 2) android.telephony.ims.ImsService implementation through ImsResolver.
*/
- private void createImsService(boolean checkService) {
- if (checkService) {
- IBinder binder = ServiceManager.checkService(getImsServiceName(mPhoneId));
-
- if (binder == null) {
- return;
- }
+ private void createImsService() {
+ if (!mConfigDynamicBind) {
+ // Old method of binding
+ Rlog.i(TAG, "Creating ImsService using ServiceManager");
+ mImsServiceProxy = getServiceProxyCompat();
+ } else {
+ Rlog.i(TAG, "Creating ImsService using ImsResolver");
+ mImsServiceProxy = getServiceProxy();
}
+ }
- IBinder b = ServiceManager.getService(getImsServiceName(mPhoneId));
+ // Deprecated method of binding with the ImsService defined in the ServiceManager.
+ private ImsServiceProxyCompat getServiceProxyCompat() {
+ IBinder binder = ServiceManager.checkService(IMS_SERVICE);
- if (b != null) {
+ if (binder != null) {
try {
- b.linkToDeath(mDeathRecipient, 0);
+ binder.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
}
}
- mImsService = IImsService.Stub.asInterface(b);
+ return new ImsServiceProxyCompat(mPhoneId, binder);
+ }
+
+ // New method of binding with the ImsResolver
+ private ImsServiceProxy getServiceProxy() {
+ TelephonyManager tm = (TelephonyManager)
+ mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ ImsServiceProxy serviceProxy = new ImsServiceProxy(mPhoneId, ImsFeature.MMTEL);
+ // Returns null if the service is not available.
+ IImsServiceController b = tm.getImsServiceControllerAndListen(mPhoneId,
+ ImsFeature.MMTEL, serviceProxy.getListener());
+ if (b != null) {
+ serviceProxy.setBinder(b.asBinder());
+ serviceProxy.setStatusCallback(() -> mStatusCallbacks.forEach(
+ ImsServiceProxy.INotifyStatusChanged::notifyStatusChanged));
+ // Trigger the cache to be updated for feature status.
+ serviceProxy.getFeatureStatus();
+ // In order to keep backwards compatibility with other services such as RcsService,
+ // we must broadcast the IMS_SERVICE_UP intent here. If it is not ready, IMS_SERVICE_UP
+ // will be called in this::sendImsServiceIntent. IMS_SERVICE_UP is sent by ImsService
+ // in the old ImsService implementation.
+ if (serviceProxy.isBinderAlive()) {
+ Intent intent = new Intent(ACTION_IMS_SERVICE_UP);
+ intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
+ mContext.sendBroadcast(new Intent(intent));
+ }
+ } else {
+ Rlog.w(TAG, "getServiceProxy: b is null! Phone Id: " + mPhoneId);
+ }
+ return serviceProxy;
}
/**
private ImsCallSession createCallSession(int serviceId,
ImsCallProfile profile) throws ImsException {
try {
- return new ImsCallSession(mImsService.createCallSession(serviceId, profile, null));
+ return new ImsCallSession(mImsServiceProxy.createCallSession(serviceId, profile, null));
} catch (RemoteException e) {
return null;
}
checkAndThrowExceptionIfServiceUnavailable();
try {
- mImsService.turnOnIms(mPhoneId);
+ mImsServiceProxy.turnOnIms();
} catch (RemoteException e) {
throw new ImsException("turnOnIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
- 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 (mContext.getResources().getBoolean(
- com.android.internal.R.bool.imsServiceAllowTurnOff)
- && (!isWfcEnabledByPlatform(mContext)
- || !isWfcEnabledByUser(mContext))) {
- log("setAdvanced4GMode() : imsServiceAllowTurnOff -> turnOffIms");
- turnOffIms();
+ } else {
+ if (isImsTurnOffAllowed()) {
+ log("setAdvanced4GMode: turnOffIms");
+ turnOffIms();
+ }
+ setLteFeatureValues(turnOn);
}
}
checkAndThrowExceptionIfServiceUnavailable();
try {
- mImsService.turnOffIms(mPhoneId);
+ mImsServiceProxy.turnOffIms();
} catch (RemoteException e) {
throw new ImsException("turnOffIms() ", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
+ private void addToRecentDisconnectReasons(ImsReasonInfo reason) {
+ if (reason == null) return;
+ while (mRecentDisconnectReasons.size() >= MAX_RECENT_DISCONNECT_REASONS) {
+ mRecentDisconnectReasons.removeFirst();
+ }
+ mRecentDisconnectReasons.addLast(reason);
+ }
+
/**
* Death recipient class for monitoring IMS service.
*/
private class ImsServiceDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
- mImsService = null;
+ mImsServiceProxy = null;
mUt = null;
mConfig = null;
mEcbm = null;
+ mMultiEndpoint = null;
- if (mContext != null) {
+ if (mContext != null && !isDynamicBinding()) {
Intent intent = new Intent(ACTION_IMS_SERVICE_DOWN);
intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
mContext.sendBroadcast(new Intent(intent));
return (mServiceClass == serviceClass);
}
- @Override
+ @Deprecated
public void registrationConnected() {
if (DBG) {
log("registrationConnected ::");
}
if (mListener != null) {
- mListener.onImsConnected();
+ mListener.onImsConnected(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
}
}
- @Override
+ @Deprecated
public void registrationProgressing() {
if (DBG) {
log("registrationProgressing ::");
}
if (mListener != null) {
- mListener.onImsProgressing();
+ mListener.onImsProgressing(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+ }
+ }
+
+ @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(imsRadioTech);
+ }
+ }
+
+ @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(imsRadioTech);
}
}
log("registrationDisconnected :: imsReasonInfo" + imsReasonInfo);
}
+ addToRecentDisconnectReasons(imsReasonInfo);
+
if (mListener != null) {
mListener.onImsDisconnected(imsReasonInfo);
}
serviceClass + ", event=" + event);
if (mListener != null) {
- mListener.onImsConnected();
+ mListener.onImsConnected(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
}
}
}
}
+ @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);
+ }
+ }
+
+ @Override
+ public void registrationChangeFailed(int targetAccessTech, ImsReasonInfo imsReasonInfo) {
+ if (DBG) log("registrationChangeFailed :: targetAccessTech=" + targetAccessTech +
+ ", imsReasonInfo=" + imsReasonInfo);
+
+ if (mListener != null) {
+ mListener.onRegistrationChangeFailed(targetAccessTech, imsReasonInfo);
+ }
+ }
}
+
/**
* Gets the ECBM interface to request ECBM exit.
*
* @throws ImsException if getting the ECBM interface results in an error
*/
public ImsEcbm getEcbmInterface(int serviceId) throws ImsException {
- if (mEcbm == null) {
+ if (mEcbm == null || !mImsServiceProxy.isBinderAlive()) {
checkAndThrowExceptionIfServiceUnavailable();
try {
- IImsEcbm iEcbm = mImsService.getEcbmInterface(serviceId);
+ IImsEcbm iEcbm = mImsServiceProxy.getEcbmInterface();
if (iEcbm == null) {
throw new ImsException("getEcbmInterface()",
}
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 || !mImsServiceProxy.isBinderAlive()) {
+ checkAndThrowExceptionIfServiceUnavailable();
+
+ try {
+ IImsMultiEndpoint iImsMultiEndpoint = mImsServiceProxy.getMultiEndpointInterface();
+
+ 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(" mImsServiceProxy = " + mImsServiceProxy);
+ 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();
+ }
}