blob: a5d4e48574c32cbc952da2403539db8e231c0e7d [file] [log] [blame]
/*
* Copyright (c) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ims;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsSmsListener;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.util.Log;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsUt;
import android.telephony.ims.ImsCallSession;
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.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Provides APIs for IMS services, such as initiating IMS calls, and provides access to
* the operator's IMS network. This class is the starting point for any IMS actions.
* You can acquire an instance of it with {@link #getInstance getInstance()}.</p>
* <p>The APIs in this class allows you to:</p>
*
* @hide
*/
public class ImsManager {
/*
* Debug flag to override configuration flag
*/
public static final String PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE = "persist.dbg.volte_avail_ovr";
public static final int PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE_DEFAULT = 0;
public static final String PROPERTY_DBG_VT_AVAIL_OVERRIDE = "persist.dbg.vt_avail_ovr";
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;
/**
* The result code to be sent back with the incoming call {@link PendingIntent}.
* @see #open(MmTelFeature.Listener)
*/
public static final int INCOMING_CALL_RESULT_CODE = 101;
/**
* Key to retrieve the call ID from an incoming call intent.
* @see #open(MmTelFeature.Listener)
*/
public static final String EXTRA_CALL_ID = "android:imsCallID";
/**
* Action to broadcast when ImsService is up.
* Internal use only.
* @deprecated
* @hide
*/
public static final String ACTION_IMS_SERVICE_UP =
"com.android.ims.IMS_SERVICE_UP";
/**
* Action to broadcast when ImsService is down.
* Internal use only.
* @deprecated
* @hide
*/
public static final String ACTION_IMS_SERVICE_DOWN =
"com.android.ims.IMS_SERVICE_DOWN";
/**
* Action to broadcast when ImsService registration fails.
* Internal use only.
* @hide
*/
public static final String ACTION_IMS_REGISTRATION_ERROR =
"com.android.ims.REGISTRATION_ERROR";
/**
* Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
* A long value; the phone ID corresponding to the IMS service coming up or down.
* Internal use only.
* @hide
*/
public static final String EXTRA_PHONE_ID = "android:phone_id";
/**
* Action for the incoming call intent for the Phone app.
* Internal use only.
* @hide
*/
public static final String ACTION_IMS_INCOMING_CALL =
"com.android.ims.IMS_INCOMING_CALL";
/**
* Part of the ACTION_IMS_INCOMING_CALL intents.
* An integer value; service identifier obtained from {@link ImsManager#open}.
* Internal use only.
* @hide
*/
public static final String EXTRA_SERVICE_ID = "android:imsServiceId";
/**
* Part of the ACTION_IMS_INCOMING_CALL intents.
* An boolean value; Flag to indicate that the incoming call is a normal call or call for USSD.
* The value "true" indicates that the incoming call is for USSD.
* Internal use only.
* @hide
*/
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 int SYSTEM_PROPERTY_NOT_SET = -1;
// -1 indicates a subscriptionProperty value that is never set.
private static final int SUB_PROPERTY_NOT_INITIALIZED = -1;
private static final String TAG = "ImsManager";
private static final boolean DBG = true;
/**
* Helper class for managing a connection to the ImsManager when the ImsService is unavailable
* or switches to another service.
*/
public static class Connector extends Handler {
// Initial condition for ims connection retry.
private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms
// Ceiling bitshift amount for service query timeout, calculated as:
// 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where
// mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT].
private static final int CEILING_SERVICE_RETRY_COUNT = 6;
private final Runnable mGetServiceRunnable = () -> {
try {
getImsService();
} catch (ImsException e) {
retryGetImsService();
}
};
public interface Listener {
/**
* ImsManager is connected to the underlying IMS implementation.
*/
void connectionReady(ImsManager manager) throws ImsException;
/**
* The underlying IMS implementation is unavailable and can not be used to communicate.
*/
void connectionUnavailable();
}
@VisibleForTesting
public interface RetryTimeout {
int get();
}
// Callback fires when ImsManager MMTel Feature changes state
private MmTelFeatureConnection.IFeatureUpdate mNotifyStatusChangedCallback =
new MmTelFeatureConnection.IFeatureUpdate() {
@Override
public void notifyStateChanged() {
try {
int status = ImsFeature.STATE_UNAVAILABLE;
synchronized (mLock) {
if (mImsManager != null) {
status = mImsManager.getImsServiceState();
}
}
log("Status Changed: " + status);
switch (status) {
case ImsFeature.STATE_READY: {
notifyReady();
break;
}
case ImsFeature.STATE_INITIALIZING:
// fall through
case ImsFeature.STATE_UNAVAILABLE: {
notifyNotReady();
break;
}
default: {
Log.w(TAG, "Unexpected State!");
}
}
} catch (ImsException e) {
loge("notifyStateChanged(): ", e);
// Could not get the ImsService, retry!
notifyNotReady();
retryGetImsService();
}
}
@Override
public void notifyUnavailable() {
notifyNotReady();
retryGetImsService();
}
};
private final Context mContext;
private final int mPhoneId;
private final Listener mListener;
private final Object mLock = new Object();
private int mRetryCount = 0;
private ImsManager mImsManager;
@VisibleForTesting
public RetryTimeout mRetryTimeout = () -> {
synchronized (mLock) {
int timeout = (1 << mRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS;
if (mRetryCount <= CEILING_SERVICE_RETRY_COUNT) {
mRetryCount++;
}
return timeout;
}
};
public Connector(Context context, int phoneId, Listener listener) {
mContext = context;
mPhoneId = phoneId;
mListener = listener;
}
public Connector(Context context, int phoneId, Listener listener, Looper looper) {
super(looper);
mContext = context;
mPhoneId = phoneId;
mListener= listener;
}
/**
* Start the creation of a connection to the underlying ImsService implementation. When the
* service is connected, {@link Listener#connectionReady(ImsManager)} will be called with
* an active ImsManager instance.
*/
public void connect() {
mRetryCount = 0;
// Send a message to connect to the Ims Service and open a connection through
// getImsService().
post(mGetServiceRunnable);
}
/**
* Disconnect from the ImsService Implementation and clean up. When this is complete,
* {@link Listener#connectionUnavailable()} will be called one last time.
*/
public void disconnect() {
removeCallbacks(mGetServiceRunnable);
synchronized (mLock) {
if (mImsManager != null) {
mImsManager.removeNotifyStatusChangedCallback(mNotifyStatusChangedCallback);
}
}
notifyNotReady();
}
private void retryGetImsService() {
synchronized (mLock) {
// remove callback so we do not receive updates from old ImsServiceProxy when
// switching between ImsServices.
mImsManager.removeNotifyStatusChangedCallback(mNotifyStatusChangedCallback);
//Leave mImsManager as null, then CallStateException will be thrown when dialing
mImsManager = null;
}
// Exponential backoff during retry, limited to 32 seconds.
loge("Connector: Retrying getting ImsService...");
removeCallbacks(mGetServiceRunnable);
postDelayed(mGetServiceRunnable, mRetryTimeout.get());
}
private void getImsService() throws ImsException {
if (DBG) log("Connector: getImsService");
synchronized (mLock) {
mImsManager = ImsManager.getInstance(mContext, mPhoneId);
// Adding to set, will be safe adding multiple times. If the ImsService is not
// active yet, this method will throw an ImsException.
mImsManager.addNotifyStatusChangedCallbackIfAvailable(mNotifyStatusChangedCallback);
}
// Wait for ImsService.STATE_READY to start listening for calls.
// Call the callback right away for compatibility with older devices that do not use
// states.
mNotifyStatusChangedCallback.notifyStateChanged();
}
private void notifyReady() throws ImsException {
ImsManager manager;
synchronized (mLock) {
manager = mImsManager;
}
try {
mListener.connectionReady(manager);
}
catch (ImsException e) {
Log.w(TAG, "Connector: notifyReady exception: " + e.getMessage());
throw e;
}
// Only reset retry count if connectionReady does not generate an ImsException/
synchronized (mLock) {
mRetryCount = 0;
}
}
private void notifyNotReady() {
mListener.connectionUnavailable();
}
}
private static HashMap<Integer, ImsManager> sImsManagerInstances =
new HashMap<Integer, ImsManager>();
private Context mContext;
private CarrierConfigManager mConfigManager;
private int mPhoneId;
private final boolean mConfigDynamicBind;
private @Nullable MmTelFeatureConnection mMmTelFeatureConnection = 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<MmTelFeatureConnection.IFeatureUpdate> mStatusCallbacks =
new CopyOnWriteArraySet<>();
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.
*
* @param context application context for creating the manager object
* @param phoneId the phone ID for the IMS Service
* @return the manager instance corresponding to the phoneId
*/
public static ImsManager getInstance(Context context, int phoneId) {
synchronized (sImsManagerInstances) {
if (sImsManagerInstances.containsKey(phoneId)) {
ImsManager m = sImsManagerInstances.get(phoneId);
// May be null for some tests
if (m != null) {
m.connectIfServiceIsAvailable();
}
return m;
}
ImsManager mgr = new ImsManager(context, phoneId);
sImsManagerInstances.put(phoneId, mgr);
return mgr;
}
}
/**
* Returns the user configuration of Enhanced 4G LTE Mode setting.
*
* @deprecated Doesn't support MSIM devices. Use
* {@link #isEnhanced4gLteModeSettingEnabledByUser()} instead.
*/
public static boolean isEnhanced4gLteModeSettingEnabledByUser(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isEnhanced4gLteModeSettingEnabledByUser();
}
loge("isEnhanced4gLteModeSettingEnabledByUser: ImsManager null, returning default value.");
return false;
}
/**
* Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If the option is
* not editable ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false),
* this method will return default value specified by
* {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*/
public boolean isEnhanced4gLteModeSettingEnabledByUser() {
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
boolean onByDefault = getBooleanCarrierConfig(
CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
// If Enhanced 4G LTE Mode is uneditable or not initialized, we use the default value
if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
|| setting == SUB_PROPERTY_NOT_INITIALIZED) {
return onByDefault;
} else {
return (setting == ImsConfig.FeatureValueConstants.ON);
}
}
/**
* Change persistent Enhanced 4G LTE Mode setting.
*
* @deprecated Doesn't support MSIM devices. Use {@link #setEnhanced4gLteModeSetting(boolean)}
* instead.
*/
public static void setEnhanced4gLteModeSetting(Context context, boolean enabled) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setEnhanced4gLteModeSetting(enabled);
}
loge("setEnhanced4gLteModeSetting: ImsManager null, value not set.");
}
/**
* Change persistent Enhanced 4G LTE Mode setting. If the option is not editable
* ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), this method will
* set the setting to the default value specified by
* {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*
*/
public void setEnhanced4gLteModeSetting(boolean enabled) {
// If editable=false, we must keep default advanced 4G mode.
if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
enabled = getBooleanCarrierConfig(
CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
}
int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
if (prevSetting != (enabled ?
ImsConfig.FeatureValueConstants.ON :
ImsConfig.FeatureValueConstants.OFF)) {
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
if (isNonTtyOrTtyOnVolteEnabled()) {
try {
setAdvanced4GMode(enabled);
} catch (ImsException ie) {
// do nothing
}
}
}
}
/**
* Indicates whether the call is non-TTY or if TTY - whether TTY on VoLTE is
* supported.
* @deprecated Does not support MSIM devices. Please use
* {@link #isNonTtyOrTtyOnVolteEnabled()} instead.
*/
public static boolean isNonTtyOrTtyOnVolteEnabled(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isNonTtyOrTtyOnVolteEnabled();
}
loge("isNonTtyOrTtyOnVolteEnabled: ImsManager null, returning default value.");
return false;
}
/**
* Indicates whether the call is non-TTY or if TTY - whether TTY on VoLTE is
* supported on a per slot basis.
*/
public boolean isNonTtyOrTtyOnVolteEnabled() {
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
return true;
}
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
if (tm == null) {
Log.w(TAG, "isNonTtyOrTtyOnVolteEnabled: telecom not available");
return true;
}
return tm.getCurrentTtyMode() == TelecomManager.TTY_MODE_OFF;
}
/**
* Returns a platform configuration for VoLTE which may override the user setting.
* @deprecated Does not support MSIM devices. Please use
* {@link #isVolteEnabledByPlatform()} instead.
*/
public static boolean isVolteEnabledByPlatform(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isVolteEnabledByPlatform();
}
loge("isVolteEnabledByPlatform: ImsManager null, returning default value.");
return false;
}
/**
* Returns a platform configuration for VoLTE which may override the user setting on a per Slot
* basis.
*/
public boolean isVolteEnabledByPlatform() {
// We first read the per slot value. If doesn't exist, we read the general value. If still
// doesn't exist, we use the hardcoded default value.
if (SystemProperties.getInt(
PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE + Integer.toString(mPhoneId),
SYSTEM_PROPERTY_NOT_SET) == 1 ||
SystemProperties.getInt(PROPERTY_DBG_VOLTE_AVAIL_OVERRIDE,
SYSTEM_PROPERTY_NOT_SET) == 1) {
return true;
}
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_device_volte_available)
&& getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
&& isGbaValid();
}
/**
* Indicates whether VoLTE is provisioned on device.
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isVolteProvisionedOnDevice()} instead.
*/
public static boolean isVolteProvisionedOnDevice(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isVolteProvisionedOnDevice();
}
loge("isVolteProvisionedOnDevice: ImsManager null, returning default value.");
return true;
}
/**
* Indicates whether VoLTE is provisioned on this slot.
*/
public boolean isVolteProvisionedOnDevice() {
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
return isVolteProvisioned();
}
return true;
}
/**
* Indicates whether VoWifi is provisioned on device.
*
* When CarrierConfig KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL is true, and VoLTE is not
* provisioned on device, this method returns false.
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isWfcProvisionedOnDevice()} instead.
*/
public static boolean isWfcProvisionedOnDevice(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isWfcProvisionedOnDevice();
}
loge("isWfcProvisionedOnDevice: ImsManager null, returning default value.");
return true;
}
/**
* Indicates whether VoWifi is provisioned on slot.
*
* When CarrierConfig KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL is true, and VoLTE is not
* provisioned on device, this method returns false.
*/
public boolean isWfcProvisionedOnDevice() {
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL)) {
if (!isVolteProvisionedOnDevice()) {
return false;
}
}
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
return isWfcProvisioned();
}
return true;
}
/**
* Indicates whether VT is provisioned on device
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isVtProvisionedOnDevice()} instead.
*/
public static boolean isVtProvisionedOnDevice(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isVtProvisionedOnDevice();
}
loge("isVtProvisionedOnDevice: ImsManager null, returning default value.");
return true;
}
/**
* Indicates whether VT is provisioned on slot.
*/
public boolean isVtProvisionedOnDevice() {
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL)) {
return isVtProvisioned();
}
return true;
}
/**
* Returns a platform configuration for VT which may override the user setting.
*
* Note: VT presumes that VoLTE is enabled (these are configuration settings
* which must be done correctly).
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isVtEnabledByPlatform()} instead.
*/
public static boolean isVtEnabledByPlatform(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isVtEnabledByPlatform();
}
loge("isVtEnabledByPlatform: ImsManager null, returning default value.");
return false;
}
/**
* Returns a platform configuration for VT which may override the user setting.
*
* Note: VT presumes that VoLTE is enabled (these are configuration settings
* which must be done correctly).
*/
public boolean isVtEnabledByPlatform() {
// We first read the per slot value. If doesn't exist, we read the general value. If still
// doesn't exist, we use the hardcoded default value.
if (SystemProperties.getInt(PROPERTY_DBG_VT_AVAIL_OVERRIDE +
Integer.toString(mPhoneId), SYSTEM_PROPERTY_NOT_SET) == 1 ||
SystemProperties.getInt(
PROPERTY_DBG_VT_AVAIL_OVERRIDE, SYSTEM_PROPERTY_NOT_SET) == 1) {
return true;
}
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_device_vt_available) &&
getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
isGbaValid();
}
/**
* Returns the user configuration of VT setting
* @deprecated Does not support MSIM devices. Please use
* {@link #isVtEnabledByUser()} instead.
*/
public static boolean isVtEnabledByUser(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isVtEnabledByUser();
}
loge("isVtEnabledByUser: ImsManager null, returning default value.");
return false;
}
/**
* Returns the user configuration of VT setting per slot. If not set, it
* returns true as default value.
*/
public boolean isVtEnabledByUser() {
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.VT_IMS_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// If it's never set, by default we return true.
return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
}
/**
* Change persistent VT enabled setting
*
* @deprecated Does not support MSIM devices. Please use {@link #setVtSetting(boolean)} instead.
*/
public static void setVtSetting(Context context, boolean enabled) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setVtSetting(enabled);
}
loge("setVtSetting: ImsManager null, can not set value.");
}
/**
* Change persistent VT enabled setting for slot.
*/
public void setVtSetting(boolean enabled) {
SubscriptionManager.setSubscriptionProperty(getSubId(), SubscriptionManager.VT_IMS_ENABLED,
booleanToPropertyString(enabled));
try {
changeMmTelCapability(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE, enabled);
if (enabled) {
log("setVtSetting(b) : turnOnIms");
turnOnIms();
} else if (isVolteEnabledByPlatform()
&& (!isVolteEnabledByPlatform()
|| !isEnhanced4gLteModeSettingEnabledByUser())) {
log("setVtSetting(b) : imsServiceAllowTurnOff -> turnOffIms");
turnOffIms();
}
} catch (ImsException | RemoteException e) {
// The ImsService is down. Since the SubscriptionManager already recorded the user's
// preference, it will be resent in updateImsServiceConfig when the ImsPhoneCallTracker
// reconnects.
loge("setVtSetting(b): ", e);
}
}
/**
* Returns whether turning off ims is allowed by platform.
* The platform property may override the carrier config.
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isTurnOffImsAllowedByPlatform()} instead.
*/
private static boolean isTurnOffImsAllowedByPlatform(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isTurnOffImsAllowedByPlatform();
}
loge("isTurnOffImsAllowedByPlatform: ImsManager null, returning default value.");
return true;
}
/**
* Returns whether turning off ims is allowed by platform.
* The platform property may override the carrier config.
*/
private boolean isTurnOffImsAllowedByPlatform() {
// We first read the per slot value. If doesn't exist, we read the general value. If still
// doesn't exist, we use the hardcoded default value.
if (SystemProperties.getInt(PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE +
Integer.toString(mPhoneId), SYSTEM_PROPERTY_NOT_SET) == 1 ||
SystemProperties.getInt(
PROPERTY_DBG_ALLOW_IMS_OFF_OVERRIDE, SYSTEM_PROPERTY_NOT_SET) == 1) {
return true;
}
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL);
}
/**
* Returns the user configuration of WFC setting
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isWfcEnabledByUser()} instead.
*/
public static boolean isWfcEnabledByUser(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isWfcEnabledByUser();
}
loge("isWfcEnabledByUser: ImsManager null, returning default value.");
return true;
}
/**
* Returns the user configuration of WFC setting for slot. If not set, it
* queries CarrierConfig value as default.
*/
public boolean isWfcEnabledByUser() {
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.WFC_IMS_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
} else {
return setting == 1;
}
}
/**
* Change persistent WFC enabled setting.
* @deprecated Does not support MSIM devices. Please use
* {@link #setWfcSetting} instead.
*/
public static void setWfcSetting(Context context, boolean enabled) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setWfcSetting(enabled);
}
loge("setWfcSetting: ImsManager null, can not set value.");
}
/**
* Change persistent WFC enabled setting for slot.
*/
public void setWfcSetting(boolean enabled) {
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_ENABLED, booleanToPropertyString(enabled));
setWfcNonPersistent(enabled, getWfcMode());
}
/**
* Non-persistently change WFC enabled setting and WFC mode for slot
*
* @param wfcMode The WFC preference if WFC is enabled
*/
public void setWfcNonPersistent(boolean enabled, int wfcMode) {
// Force IMS to register over LTE when turning off WFC
int imsWfcModeFeatureValue =
enabled ? wfcMode : ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
try {
changeMmTelCapability(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, enabled);
if (enabled) {
log("setWfcSetting() : turnOnIms");
turnOnIms();
} else if (isTurnOffImsAllowedByPlatform()
&& (!isVolteEnabledByPlatform()
|| !isEnhanced4gLteModeSettingEnabledByUser())) {
log("setWfcSetting() : imsServiceAllowTurnOff -> turnOffIms");
turnOffIms();
}
setWfcModeInternal(imsWfcModeFeatureValue);
} catch (ImsException | RemoteException e) {
loge("setWfcSetting(): ", e);
}
}
/**
* Returns the user configuration of WFC preference setting.
*
* @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode(boolean roaming)} instead.
*/
public static int getWfcMode(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.getWfcMode();
}
loge("getWfcMode: ImsManager null, returning default value.");
return ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY;
}
/**
* Returns the user configuration of WFC preference setting
* @deprecated. Use {@link #getWfcMode(boolean roaming)} instead.
*/
public int getWfcMode() {
return getWfcMode(false);
}
/**
* Change persistent WFC preference setting.
*
* @deprecated Doesn't support MSIM devices. Use {@link #setWfcMode(int)} instead.
*/
public static void setWfcMode(Context context, int wfcMode) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setWfcMode(wfcMode);
}
loge("setWfcMode: ImsManager null, can not set value.");
}
/**
* Change persistent WFC preference setting for slot.
*/
public void setWfcMode(int wfcMode) {
if (DBG) log("setWfcMode(i) - setting=" + wfcMode);
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
setWfcModeInternal(wfcMode);
}
/**
* Changes the WFC mode to its default value for Carriers with non-editable WFC settings.
*/
private void updateDefaultWfcMode() {
if (DBG) log("updateDefaultWfcMode");
if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL)) {
setWfcMode(getIntCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
}
}
/**
* Returns the user configuration of WFC preference setting
*
* @param roaming {@code false} for home network setting, {@code true} for roaming setting
*
* @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode(boolean)} instead.
*/
public static int getWfcMode(Context context, boolean roaming) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.getWfcMode(roaming);
}
loge("getWfcMode: ImsManager null, returning default value.");
return ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY;
}
/**
* Returns the user configuration of WFC preference setting for slot. If not set, it
* queries CarrierConfig value as default.
*
* @param roaming {@code false} for home network setting, {@code true} for roaming setting
*/
public int getWfcMode(boolean roaming) {
int setting = 0;
if (!roaming) {
setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.WFC_IMS_MODE,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
setting = getIntCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT);
}
if (DBG) log("getWfcMode - setting=" + setting);
} else {
setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.WFC_IMS_ROAMING_MODE,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
// SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
setting = getIntCarrierConfig(
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
*
* @deprecated Doesn't support MSIM devices. Please use {@link #setWfcMode(int, boolean)}
* instead.
*/
public static void setWfcMode(Context context, int wfcMode, boolean roaming) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setWfcMode(wfcMode, roaming);
}
loge("setWfcMode: ImsManager null, can not set value.");
}
/**
* Change persistent WFC preference setting
*
* @param roaming {@code false} for home network setting, {@code true} for roaming setting
*/
public void setWfcMode(int wfcMode, boolean roaming) {
if (!roaming) {
if (DBG) log("setWfcMode(i,b) - setting=" + wfcMode);
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
} else {
if (DBG) log("setWfcMode(i,b) (roaming) - setting=" + wfcMode);
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_ROAMING_MODE, Integer.toString(wfcMode));
}
TelephonyManager tm = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (roaming == tm.isNetworkRoaming(getSubId())) {
setWfcModeInternal(wfcMode);
}
}
private int getSubId() {
int[] subIds = SubscriptionManager.getSubId(mPhoneId);
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
if (subIds != null && subIds.length >= 1) {
subId = subIds[0];
}
return subId;
}
private static void setWfcModeInternal(Context context, int wfcMode) {
final ImsManager imsManager = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (imsManager != null) {
final int value = wfcMode;
Thread thread = new Thread(new Runnable() {
public void run() {
try {
imsManager.getConfigInterface().setConfig(
ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE,
value);
} catch (ImsException e) {
// do nothing
}
}
});
thread.start();
}
}
private void setWfcModeInternal(int wfcMode) {
final int value = wfcMode;
Thread thread = new Thread(() -> {
try {
getConfigInterface().setConfig(
ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE, value);
} catch (ImsException e) {
// do nothing
}
});
thread.start();
}
/**
* Returns the user configuration of WFC roaming setting
*
* @deprecated Does not support MSIM devices. Please use
* {@link #isWfcRoamingEnabledByUser()} instead.
*/
public static boolean isWfcRoamingEnabledByUser(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isWfcRoamingEnabledByUser();
}
loge("isWfcRoamingEnabledByUser: ImsManager null, returning default value.");
return false;
}
/**
* Returns the user configuration of WFC roaming setting for slot. If not set, it
* queries CarrierConfig value as default.
*/
public boolean isWfcRoamingEnabledByUser() {
int setting = SubscriptionManager.getIntegerSubscriptionProperty(
getSubId(), SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
SUB_PROPERTY_NOT_INITIALIZED, mContext);
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
return getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
} else {
return (setting == 1);
}
}
/**
* Change persistent WFC roaming enabled setting
*/
public static void setWfcRoamingSetting(Context context, boolean enabled) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.setWfcRoamingSetting(enabled);
}
loge("setWfcRoamingSetting: ImsManager null, value not set.");
}
/**
* Change persistent WFC roaming enabled setting
*/
public void setWfcRoamingSetting(boolean enabled) {
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_ROAMING_ENABLED, booleanToPropertyString(enabled)
);
setWfcRoamingSettingInternal(enabled);
}
private void setWfcRoamingSettingInternal(boolean enabled) {
final int value = enabled
? ImsConfig.FeatureValueConstants.ON
: ImsConfig.FeatureValueConstants.OFF;
Thread thread = new Thread(() -> {
try {
getConfigInterface().setConfig(
ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING, value);
} catch (ImsException e) {
// do nothing
}
});
thread.start();
}
/**
* Returns a platform configuration for WFC which may override the user
* setting. Note: WFC presumes that VoLTE is enabled (these are
* configuration settings which must be done correctly).
*
* @deprecated Doesn't work for MSIM devices. Use {@link #isWfcEnabledByPlatform()}
* instead.
*/
public static boolean isWfcEnabledByPlatform(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
return mgr.isWfcEnabledByPlatform();
}
loge("isWfcEnabledByPlatform: ImsManager null, returning default value.");
return false;
}
/**
* Returns a platform configuration for WFC which may override the user
* setting per slot. Note: WFC presumes that VoLTE is enabled (these are
* configuration settings which must be done correctly).
*/
public boolean isWfcEnabledByPlatform() {
// We first read the per slot value. If doesn't exist, we read the general value. If still
// doesn't exist, we use the hardcoded default value.
if (SystemProperties.getInt(PROPERTY_DBG_WFC_AVAIL_OVERRIDE +
Integer.toString(mPhoneId), SYSTEM_PROPERTY_NOT_SET) == 1 ||
SystemProperties.getInt(
PROPERTY_DBG_WFC_AVAIL_OVERRIDE, SYSTEM_PROPERTY_NOT_SET) == 1) {
return true;
}
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_device_wfc_ims_available) &&
getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL) &&
isGbaValid();
}
/**
* 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 boolean isGbaValid() {
if (getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL)) {
final TelephonyManager telephonyManager = new TelephonyManager(mContext, getSubId());
String efIst = telephonyManager.getIsimIst();
if (efIst == null) {
loge("isGbaValid - ISF is NULL");
return true;
}
boolean result = efIst != null && efIst.length() > 1 &&
(0x02 & (byte)efIst.charAt(1)) != 0;
if (DBG) log("isGbaValid - GBA capable=" + result + ", ISF=" + efIst);
return result;
}
return true;
}
/**
* Will return with config value or throw an ImsException if we receive an error from
* ImsConfig for that value.
*/
private boolean getProvisionedBool(ImsConfig config, int item) throws ImsException {
int value = config.getProvisionedValue(item);
if (value == ImsConfig.OperationStatusConstants.UNKNOWN) {
throw new ImsException("getProvisionedBool failed with error for item: " + item,
ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR);
}
return config.getProvisionedValue(item) == ImsConfig.FeatureValueConstants.ON;
}
/**
* Will return with config value or return false if we receive an error from
* ImsConfig for that value.
*/
private boolean getProvisionedBoolNoException(int item) {
try {
ImsConfig config = getConfigInterface();
return getProvisionedBool(config, item);
} catch (ImsException ex) {
return false;
}
}
/**
* Sync carrier config and user settings with ImsConfig.
*
* @param context for the manager object
* @param phoneId phone id
* @param force update
*
* @deprecated Doesn't support MSIM devices. Use {@link #updateImsServiceConfig(boolean)}
* instead.
*/
public static void updateImsServiceConfig(Context context, int phoneId, boolean force) {
ImsManager mgr = ImsManager.getInstance(context, phoneId);
if (mgr != null) {
mgr.updateImsServiceConfig(force);
}
loge("updateImsServiceConfig: ImsManager null, returning without update.");
}
/**
* Sync carrier config and user settings with ImsConfig.
*
* @param force update
*/
public void updateImsServiceConfig(boolean force) {
if (!force) {
TelephonyManager tm = new TelephonyManager(mContext, getSubId());
if (tm.getSimState() != TelephonyManager.SIM_STATE_READY) {
log("updateImsServiceConfig: SIM not ready");
// Don't disable IMS if SIM is not ready
return;
}
}
if (!mConfigUpdated || force) {
try {
// 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
// changeEnabledCapabilities() function calls from setAdvanced4GMode(). This is done
// to differentiate this code path from vendor code perspective.
boolean isImsUsed = updateVolteFeatureValue();
isImsUsed |= updateWfcFeatureAndProvisionedValues();
isImsUsed |= updateVideoCallFeatureValue();
if (isImsUsed || !isTurnOffImsAllowedByPlatform()) {
// 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");
turnOnIms();
} else {
// Turn off IMS if it is not used AND turning off is allowed for carrier.
log("updateImsServiceConfig: turnOffIms");
turnOffIms();
}
mConfigUpdated = true;
} catch (ImsException | RemoteException e) {
loge("updateImsServiceConfig: ", e);
mConfigUpdated = false;
}
}
}
/**
* Update VoLTE config
* @return whether feature is On
* @throws ImsException
*/
private boolean updateVolteFeatureValue() throws RemoteException {
boolean available = isVolteEnabledByPlatform();
boolean enabled = isEnhanced4gLteModeSettingEnabledByUser();
boolean isNonTty = isNonTtyOrTtyOnVolteEnabled();
boolean isFeatureOn = available && enabled && isNonTty;
log("updateVolteFeatureValue: available = " + available
+ ", enabled = " + enabled
+ ", nonTTY = " + isNonTty);
changeMmTelCapability(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE, isFeatureOn);
return isFeatureOn;
}
/**
* Update video call over LTE config
* @return whether feature is On
* @throws ImsException
*/
private boolean updateVideoCallFeatureValue() throws RemoteException {
boolean available = isVtEnabledByPlatform();
boolean enabled = isVtEnabledByUser();
boolean isNonTty = isNonTtyOrTtyOnVolteEnabled();
boolean isDataEnabled = isDataEnabled();
boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(
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);
changeMmTelCapability(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE, isFeatureOn);
return isFeatureOn;
}
/**
* Update WFC config
* @return whether feature is On
* @throws ImsException
*/
private boolean updateWfcFeatureAndProvisionedValues() throws RemoteException {
TelephonyManager tm = new TelephonyManager(mContext, getSubId());
boolean isNetworkRoaming = tm.isNetworkRoaming();
boolean available = isWfcEnabledByPlatform();
boolean enabled = isWfcEnabledByUser();
updateDefaultWfcMode();
int mode = getWfcMode(isNetworkRoaming);
boolean roaming = isWfcRoamingEnabledByUser();
boolean isFeatureOn = available && enabled;
log("updateWfcFeatureAndProvisionedValues: available = " + available
+ ", enabled = " + enabled
+ ", mode = " + mode
+ ", roaming = " + roaming);
changeMmTelCapability(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, isFeatureOn);
if (!isFeatureOn) {
mode = ImsConfig.WfcModeFeatureValueConstants.CELLULAR_PREFERRED;
roaming = false;
}
setWfcModeInternal(mContext, mode);
setWfcRoamingSettingInternal(roaming);
return isFeatureOn;
}
/**
* Do NOT use this directly, instead use {@link #getInstance(Context, int)}.
*/
@VisibleForTesting
public ImsManager(Context context, int phoneId) {
mContext = context;
mPhoneId = phoneId;
mConfigDynamicBind = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dynamic_bind_ims);
mConfigManager = (CarrierConfigManager) context.getSystemService(
Context.CARRIER_CONFIG_SERVICE);
createImsService();
}
/**
* @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. If it is not available or
* busy, it will try to connect before reporting failure.
*/
public boolean isServiceAvailable() {
// If we are busy resolving dynamic IMS bindings, we are not available yet.
TelephonyManager tm = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
if (tm.isResolvingImsBinding()) {
Log.d(TAG, "isServiceAvailable: resolving IMS binding, returning false");
return false;
}
connectIfServiceIsAvailable();
// mImsServiceProxy will always create an ImsServiceProxy.
return mMmTelFeatureConnection.isBinderAlive();
}
/*
* Returns a flag indicating whether the IMS service is ready to send requests to lower layers.
*/
public boolean isServiceReady() {
connectIfServiceIsAvailable();
return mMmTelFeatureConnection.isBinderReady();
}
/**
* If the service is available, try to reconnect.
*/
public void connectIfServiceIsAvailable() {
if (mMmTelFeatureConnection == null || !mMmTelFeatureConnection.isBinderAlive()) {
createImsService();
}
}
public void setConfigListener(ImsConfigListener listener) {
mImsConfigListener = listener;
}
/**
* Adds a callback for status changed events if the binder is already available. If it is not,
* this method will throw an ImsException.
*/
@VisibleForTesting
public void addNotifyStatusChangedCallbackIfAvailable(MmTelFeatureConnection.IFeatureUpdate c)
throws ImsException {
if (!mMmTelFeatureConnection.isBinderAlive()) {
throw new ImsException("Binder is not active!",
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
if (c != null) {
mStatusCallbacks.add(c);
}
}
void removeNotifyStatusChangedCallback(MmTelFeatureConnection.IFeatureUpdate c) {
if (c != null) {
mStatusCallbacks.remove(c);
} else {
Log.w(TAG, "removeNotifyStatusChangedCallback: callback is null!");
}
}
/**
* Opens the IMS service for making calls and/or receiving generic IMS calls.
* The caller may make subsequent calls through {@link #makeCall}.
* The IMS service will register the device to the operator's network with the credentials
* (from ISIM) periodically in order to receive calls from the operator's network.
* When the IMS service receives a new call, it will call
* {@link MmTelFeature.Listener#onIncomingCall}
* The listener contains a call ID extra {@link #getCallId} and it can be used to take a call.
* @param listener A {@link MmTelFeature.Listener}, which is the interface the
* {@link MmTelFeature} uses to notify the framework of updates
* @throws NullPointerException if {@code listener} is null
* @throws ImsException if calling the IMS service results in an error
* @see #getCallId
*/
public void open(MmTelFeature.Listener listener) throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
if (listener == null) {
throw new NullPointerException("listener can't be null");
}
try {
mMmTelFeatureConnection.openConnection(listener);
} catch (RemoteException e) {
throw new ImsException("open()", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
/**
* 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
*
* @deprecated Use {@link #addRegistrationListener(ImsConnectionStateListener)} instead.
*/
public void addRegistrationListener(int serviceClass, ImsConnectionStateListener listener)
throws ImsException {
addRegistrationListener(listener);
}
/**
* Adds registration listener to the IMS service.
*
* @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
* @deprecated use {@link #addRegistrationCallback(ImsRegistrationImplBase.Callback)} and
* {@link #addCapabilitiesCallback(ImsFeature.CapabilityCallback)} instead.
*/
public void addRegistrationListener(ImsConnectionStateListener listener) throws ImsException {
if (listener == null) {
throw new NullPointerException("listener can't be null");
}
addRegistrationCallback(listener);
// connect the ImsConnectionStateListener to the new CapabilityCallback.
addCapabilitiesCallback(new ImsFeature.CapabilityCallback() {
@Override
public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
listener.onFeatureCapabilityChangedAdapter(getRegistrationTech(), config);
}
});
log("Registration Callback registered.");
}
/**
* Adds a callback that gets called when IMS registration has changed.
* @param callback A {@link ImsRegistrationImplBase.Callback} that will notify the caller when
* IMS registration status has changed.
* @throws ImsException when the ImsService connection is not available.
*/
public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
throws ImsException {
if (callback == null) {
throw new NullPointerException("registration callback can't be null");
}
checkAndThrowExceptionIfServiceUnavailable();
try {
mMmTelFeatureConnection.addRegistrationCallback(callback);
log("Registration Callback registered.");
// Only record if there isn't a RemoteException.
} catch (RemoteException e) {
throw new ImsException("addRegistrationCallback(IRIB)", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
/**
* Adds a callback that gets called when MMTel capability status has changed, for example when
* Voice over IMS or VT over IMS is not available currently.
* @param callback A {@link ImsFeature.CapabilityCallback} that will notify the caller when
* MMTel capability status has changed.
* @throws ImsException when the ImsService connection is not available.
*/
public void addCapabilitiesCallback(ImsFeature.CapabilityCallback callback)
throws ImsException {
if (callback == null) {
throw new NullPointerException("capabilities callback can't be null");
}
checkAndThrowExceptionIfServiceUnavailable();
try {
mMmTelFeatureConnection.addCapabilityCallback(callback);
log("Capability Callback registered.");
// Only record if there isn't a RemoteException.
} catch (RemoteException e) {
throw new ImsException("addCapabilitiesCallback(IF)", 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 {
if (listener == null) {
throw new NullPointerException("listener can't be null");
}
checkAndThrowExceptionIfServiceUnavailable();
try {
mMmTelFeatureConnection.removeRegistrationCallback(listener);
log("Registration Callback/Listener registered.");
// Only record if there isn't a RemoteException.
} catch (RemoteException e) {
throw new ImsException("addRegistrationCallback()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech() {
try {
return mMmTelFeatureConnection.getRegistrationTech();
} catch (RemoteException e) {
Log.w(TAG, "getRegistrationTech: no connection to ImsService.");
return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
}
}
/**
* Closes the connection and removes all active callbacks.
* All the resources that were allocated to the service are also released.
*/
public void close() {
if (mMmTelFeatureConnection != null) {
mMmTelFeatureConnection.closeConnection();
}
mUt = null;
mConfig = null;
mEcbm = null;
mMultiEndpoint = null;
}
/**
* Gets the configuration interface to provision / withdraw the supplementary service settings.
*
* @return the Ut interface instance
* @throws ImsException if getting the Ut interface results in an error
*/
public ImsUtInterface getSupplementaryServiceConfiguration() throws ImsException {
// FIXME: manage the multiple Ut interfaces based on the session id
if (mUt != null && mUt.isBinderAlive()) {
return mUt;
}
checkAndThrowExceptionIfServiceUnavailable();
try {
IImsUt iUt = mMmTelFeatureConnection.getUtInterface();
if (iUt == null) {
throw new ImsException("getSupplementaryServiceConfiguration()",
ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
}
mUt = new ImsUt(iUt);
} catch (RemoteException e) {
throw new ImsException("getSupplementaryServiceConfiguration()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
return mUt;
}
/**
* Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
*
* @param serviceType a service type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#SERVICE_TYPE_NONE}
* {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
* {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
* @param callType a call type that is specified in {@link ImsCallProfile}
* {@link ImsCallProfile#CALL_TYPE_VOICE}
* {@link ImsCallProfile#CALL_TYPE_VT}
* {@link ImsCallProfile#CALL_TYPE_VT_TX}
* {@link ImsCallProfile#CALL_TYPE_VT_RX}
* {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
* {@link ImsCallProfile#CALL_TYPE_VS}
* {@link ImsCallProfile#CALL_TYPE_VS_TX}
* {@link ImsCallProfile#CALL_TYPE_VS_RX}
* @return a {@link ImsCallProfile} object
* @throws ImsException if calling the IMS service results in an error
*/
public ImsCallProfile createCallProfile(int serviceType, int callType) throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
return mMmTelFeatureConnection.createCallProfile(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 profile a call profile to make the call
* (it contains service type, call type, media information, etc.)
* @param callees participants to invite the conference call
* @param listener 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 makeCall(ImsCallProfile profile, String[] callees,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
log("makeCall :: profile=" + profile);
}
checkAndThrowExceptionIfServiceUnavailable();
ImsCall call = new ImsCall(mContext, profile);
call.setListener(listener);
ImsCallSession session = createCallSession(profile);
if ((callees != null) && (callees.length == 1)) {
call.start(session, callees[0]);
} else {
call.start(session, callees);
}
return call;
}
/**
* Creates a {@link ImsCall} to take an incoming call.
*
* @param sessionId a session id which is obtained from {@link ImsManager#open}
* @param incomingCallExtras 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(IImsCallSession session, Bundle incomingCallExtras,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
log("takeCall :: incomingCall=" + incomingCallExtras);
}
checkAndThrowExceptionIfServiceUnavailable();
if (incomingCallExtras == null) {
throw new ImsException("Can't retrieve session with null intent",
ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
}
String callId = getCallId(incomingCallExtras);
if (callId == null) {
throw new ImsException("Call ID missing in the incoming call intent",
ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
}
try {
if (session == null) {
throw new ImsException("No pending session for the call",
ImsReasonInfo.CODE_LOCAL_NO_PENDING_CALL);
}
ImsCall call = new ImsCall(mContext, session.getCallProfile());
call.attachSession(new ImsCallSession(session));
call.setListener(listener);
return call;
} catch (Throwable t) {
throw new ImsException("takeCall()", t, ImsReasonInfo.CODE_UNSPECIFIED);
}
}
/**
* Gets the config interface to get/set service/capability parameters.
*
* @return the ImsConfig instance.
* @throws ImsException if getting the setting interface results in an error.
*/
public ImsConfig getConfigInterface() throws ImsException {
if (mConfig != null && mConfig.isBinderAlive()) {
return mConfig;
}
checkAndThrowExceptionIfServiceUnavailable();
try {
IImsConfig config = mMmTelFeatureConnection.getConfigInterface();
if (config == null) {
throw new ImsException("getConfigInterface()",
ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
}
mConfig = new ImsConfig(config, mContext);
} catch (RemoteException e) {
throw new ImsException("getConfigInterface()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
return mConfig;
}
public void changeMmTelCapability(
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech,
boolean isEnabled) throws RemoteException {
CapabilityChangeRequest request = new CapabilityChangeRequest();
if (isEnabled) {
request.addCapabilitiesToEnableForTech(capability, radioTech);
} else {
request.addCapabilitiesToDisableForTech(capability, radioTech);
}
mMmTelFeatureConnection.changeEnabledCapabilities(request, null);
if (mImsConfigListener != null) {
mImsConfigListener.onSetFeatureResponse(capability,
mMmTelFeatureConnection.getRegistrationTech(),
isEnabled ? ImsConfig.FeatureValueConstants.ON
: ImsConfig.FeatureValueConstants.OFF, -1);
}
}
public void setRttEnabled(boolean enabled) {
try {
setAdvanced4GMode(enabled || isEnhanced4gLteModeSettingEnabledByUser());
final int value = enabled ? ImsConfig.FeatureValueConstants.ON :
ImsConfig.FeatureValueConstants.OFF;
Thread thread = new Thread(() -> {
try {
Log.i(ImsManager.class.getSimpleName(), "Setting RTT enabled to " + enabled);
getConfigInterface().setProvisionedValue(
ImsConfig.ConfigConstants.RTT_SETTING_ENABLED, value);
} catch (ImsException e) {
Log.e(ImsManager.class.getSimpleName(), "Unable to set RTT enabled to "
+ enabled + ": " + e);
}
});
thread.start();
} catch (ImsException e) {
Log.e(ImsManager.class.getSimpleName(), "Unable to set RTT enabled to " + enabled
+ ": " + e);
}
}
/**
* Set the TTY mode. This is the actual tty mode (varies depending on peripheral status)
*/
public void setTtyMode(int ttyMode) throws ImsException {
if (!getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL)) {
setAdvanced4GMode((ttyMode == TelecomManager.TTY_MODE_OFF) &&
isEnhanced4gLteModeSettingEnabledByUser());
}
}
/**
* Sets the UI TTY mode. This is the preferred TTY mode that the user sets in the call
* settings screen.
*/
public void setUiTTYMode(Context context, int uiTtyMode, Message onComplete)
throws ImsException {
checkAndThrowExceptionIfServiceUnavailable();
try {
mMmTelFeatureConnection.setUiTTYMode(uiTtyMode, onComplete);
} catch (RemoteException e) {
throw new ImsException("setTTYMode()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
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 getImsServiceState() throws ImsException {
return mMmTelFeatureConnection.getFeatureState();
}
/**
* Get the boolean config from carrier config manager.
*
* @param key config key defined in CarrierConfigManager
* @return boolean value of corresponding key.
*/
private boolean getBooleanCarrierConfig(String key) {
int[] subIds = SubscriptionManager.getSubId(mPhoneId);
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
if (subIds != null && subIds.length >= 1) {
subId = subIds[0];
}
PersistableBundle b = null;
if (mConfigManager != null) {
// If an invalid subId is used, this bundle will contain default values.
b = mConfigManager.getConfigForSubId(subId);
}
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 key config key defined in CarrierConfigManager
* @return integer value of corresponding key.
*/
private int getIntCarrierConfig(String key) {
int[] subIds = SubscriptionManager.getSubId(mPhoneId);
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
if (subIds != null && subIds.length >= 1) {
subId = subIds[0];
}
PersistableBundle b = null;
if (mConfigManager != null) {
// If an invalid subId is used, this bundle will contain default values.
b = mConfigManager.getConfigForSubId(subId);
}
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 incomingCallExtras the incoming call broadcast intent
* @return the call ID or null if the intent does not contain it
*/
private static String getCallId(Bundle incomingCallExtras) {
if (incomingCallExtras == null) {
return null;
}
return incomingCallExtras.getString(EXTRA_CALL_ID);
}
/**
* Checks to see if the ImsService Binder is connected. If it is not, we try to create the
* connection again.
*/
private void checkAndThrowExceptionIfServiceUnavailable()
throws ImsException {
if (mMmTelFeatureConnection == null || !mMmTelFeatureConnection.isBinderAlive()) {
createImsService();
if (mMmTelFeatureConnection == null) {
throw new ImsException("Service is unavailable",
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
}
/**
* 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() {
Rlog.i(TAG, "Creating ImsService");
mMmTelFeatureConnection = MmTelFeatureConnection.create(mContext, mPhoneId);
// Forwarding interface to tell mStatusCallbacks that the Proxy is unavailable.
mMmTelFeatureConnection.setStatusCallback(new MmTelFeatureConnection.IFeatureUpdate() {
@Override
public void notifyStateChanged() {
mStatusCallbacks.forEach(MmTelFeatureConnection.IFeatureUpdate::notifyStateChanged);
}
@Override
public void notifyUnavailable() {
mStatusCallbacks.forEach(MmTelFeatureConnection.IFeatureUpdate::notifyUnavailable);
}
});
}
/**
* Creates a {@link ImsCallSession} with the specified call profile.
* Use other methods, if applicable, instead of interacting with
* {@link ImsCallSession} directly.
*
* @param profile a call profile to make the call
*/
private ImsCallSession createCallSession(ImsCallProfile profile) throws ImsException {
try {
// Throws an exception if the ImsService Feature is not ready to accept commands.
return new ImsCallSession(mMmTelFeatureConnection.createCallSession(profile));
} catch (RemoteException e) {
Rlog.w(TAG, "CreateCallSession: Error, remote exception: " + e.getMessage());
throw new ImsException("createCallSession()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
private static void log(String s) {
Rlog.d(TAG, s);
}
private static void loge(String s) {
Rlog.e(TAG, s);
}
private static void loge(String s, Throwable t) {
Rlog.e(TAG, s, t);
}
/**
* Used for turning on IMS.if its off already
*/
private void turnOnIms() throws ImsException {
TelephonyManager tm = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
tm.enableIms(mPhoneId);
}
private boolean isImsTurnOffAllowed() {
return isTurnOffImsAllowedByPlatform()
&& (!isWfcEnabledByPlatform()
|| !isWfcEnabledByUser());
}
private void setLteFeatureValues(boolean turnOn) {
log("setLteFeatureValues: " + turnOn);
CapabilityChangeRequest request = new CapabilityChangeRequest();
if (turnOn) {
request.addCapabilitiesToEnableForTech(
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
} else {
request.addCapabilitiesToDisableForTech(
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
}
if (isVolteEnabledByPlatform()) {
boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
boolean enableViLte = turnOn && isVtEnabledByUser() &&
(ignoreDataEnabledChanged || isDataEnabled());
if (enableViLte) {
request.addCapabilitiesToEnableForTech(
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
} else {
request.addCapabilitiesToDisableForTech(
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
}
}
try {
mMmTelFeatureConnection.changeEnabledCapabilities(request, null);
} catch (RemoteException e) {
Log.e(TAG, "setLteFeatureValues: Exception: " + e.getMessage());
}
}
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 (isImsTurnOffAllowed()) {
log("setAdvanced4GMode: turnOffIms");
turnOffIms();
}
setLteFeatureValues(turnOn);
}
}
/**
* Used for turning off IMS completely in order to make the device CSFB'ed.
* Once turned off, all calls will be over CS.
*/
private void turnOffIms() throws ImsException {
TelephonyManager tm = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
tm.disableIms(mPhoneId);
}
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() {
mMmTelFeatureConnection = null;
mUt = null;
mConfig = null;
mEcbm = null;
mMultiEndpoint = null;
}
}
/**
* Gets the ECBM interface to request ECBM exit.
*
* @return the ECBM interface instance
* @throws ImsException if getting the ECBM interface results in an error
*/
public ImsEcbm getEcbmInterface() throws ImsException {
if (mEcbm != null && mEcbm.isBinderAlive()) {
return mEcbm;
}
checkAndThrowExceptionIfServiceUnavailable();
try {
IImsEcbm iEcbm = mMmTelFeatureConnection.getEcbmInterface();
if (iEcbm == null) {
throw new ImsException("getEcbmInterface()",
ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED);
}
mEcbm = new ImsEcbm(iEcbm);
} catch (RemoteException e) {
throw new ImsException("getEcbmInterface()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
return mEcbm;
}
public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) throws ImsException {
try {
mMmTelFeatureConnection.sendSms(token, messageRef, format, smsc, isRetry, pdu);
} catch (RemoteException e) {
throw new ImsException("sendSms()", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public void acknowledgeSms(int token, int messageRef, int result) throws ImsException {
try {
mMmTelFeatureConnection.acknowledgeSms(token, messageRef, result);
} catch (RemoteException e) {
throw new ImsException("acknowledgeSms()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public void acknowledgeSmsReport(int token, int messageRef, int result) throws ImsException{
try {
mMmTelFeatureConnection.acknowledgeSmsReport(token, messageRef, result);
} catch (RemoteException e) {
throw new ImsException("acknowledgeSmsReport()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public String getSmsFormat() throws ImsException{
try {
return mMmTelFeatureConnection.getSmsFormat();
} catch (RemoteException e) {
throw new ImsException("getSmsFormat()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public void setSmsListener(IImsSmsListener listener) throws ImsException {
try {
mMmTelFeatureConnection.setSmsListener(listener);
} catch (RemoteException e) {
throw new ImsException("setSmsListener()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
public void onSmsReady() throws ImsException {
try {
mMmTelFeatureConnection.onSmsReady();
} catch (RemoteException e) {
throw new ImsException("onSmsReady()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
/**
* Determines whether or not a call with the specified numbers should be placed over IMS or over
* CSFB.
* @param isEmergency is at least one call an emergency number.
* @param numbers A {@link String} array containing the numbers in the call being placed. Can
* be multiple numbers in the case of dialing out a conference.
* @return The result of the query, one of the following values:
* - {@link MmTelFeature#PROCESS_CALL_IMS}
* - {@link MmTelFeature#PROCESS_CALL_CSFB}
* @throws ImsException if the ImsService is not available. In this case, we should fall back
* to CSFB anyway.
*/
public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
String[] numbers) throws ImsException {
try {
return mMmTelFeatureConnection.shouldProcessCall(isEmergency, numbers);
} catch (RemoteException e) {
throw new ImsException("shouldProcessCall()", e,
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
/**
* Gets the Multi-Endpoint interface to subscribe to multi-enpoint notifications..
*
* @return the multi-endpoint interface instance
* @throws ImsException if getting the multi-endpoint interface results in an error
*/
public ImsMultiEndpoint getMultiEndpointInterface() throws ImsException {
if (mMultiEndpoint != null && mMultiEndpoint.isBinderAlive()) {
return mMultiEndpoint;
}
checkAndThrowExceptionIfServiceUnavailable();
try {
IImsMultiEndpoint iImsMultiEndpoint = mMmTelFeatureConnection.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.
*
* @deprecated Doesn't support MSIM devices. Use {@link #factoryReset()} instead.
*
* @hide
*/
public static void factoryReset(Context context) {
ImsManager mgr = ImsManager.getInstance(context,
SubscriptionManager.getDefaultVoicePhoneId());
if (mgr != null) {
mgr.factoryReset();
}
loge("factoryReset: ImsManager null.");
}
/**
* Resets ImsManager settings back to factory defaults.
*
* @hide
*/
public void factoryReset() {
// Set VoLTE to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
// Set VoWiFi to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_ENABLED,
booleanToPropertyString(getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL)));
// Set VoWiFi mode to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_MODE,
Integer.toString(getIntCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)));
// Set VoWiFi roaming to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
booleanToPropertyString(getBooleanCarrierConfig(
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL)));
// Set VT to default
SubscriptionManager.setSubscriptionProperty(getSubId(),
SubscriptionManager.VT_IMS_ENABLED, booleanToPropertyString(true));
// Push settings to ImsConfig
updateImsServiceConfig(true);
}
private boolean isDataEnabled() {
return new TelephonyManager(mContext, getSubId()).isMobileDataEnabled();
}
private boolean isVolteProvisioned() {
return getProvisionedBoolNoException(
ImsConfig.ConfigConstants.VLT_SETTING_ENABLED);
}
private boolean isWfcProvisioned() {
return getProvisionedBoolNoException(
ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED);
}
private boolean isVtProvisioned() {
return getProvisionedBoolNoException(
ImsConfig.ConfigConstants.LVC_SETTING_ENABLED);
}
private static String booleanToPropertyString(boolean bool) {
return bool ? "1" : "0";
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("ImsManager:");
pw.println(" mPhoneId = " + mPhoneId);
pw.println(" mConfigUpdated = " + mConfigUpdated);
pw.println(" mImsServiceProxy = " + mMmTelFeatureConnection);
pw.println(" mDataEnabled = " + isDataEnabled());
pw.println(" ignoreDataEnabledChanged = " + getBooleanCarrierConfig(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS));
pw.println(" isGbaValid = " + isGbaValid());
pw.println(" isImsTurnOffAllowed = " + isImsTurnOffAllowed());
pw.println(" isNonTtyOrTtyOnVolteEnabled = " + isNonTtyOrTtyOnVolteEnabled());
pw.println(" isVolteEnabledByPlatform = " + isVolteEnabledByPlatform());
pw.println(" isVolteProvisionedOnDevice = " + isVolteProvisionedOnDevice());
pw.println(" isEnhanced4gLteModeSettingEnabledByUser = " +
isEnhanced4gLteModeSettingEnabledByUser());
pw.println(" isVtEnabledByPlatform = " + isVtEnabledByPlatform());
pw.println(" isVtEnabledByUser = " + isVtEnabledByUser());
pw.println(" isWfcEnabledByPlatform = " + isWfcEnabledByPlatform());
pw.println(" isWfcEnabledByUser = " + isWfcEnabledByUser());
pw.println(" getWfcMode = " + getWfcMode());
pw.println(" isWfcRoamingEnabledByUser = " + isWfcRoamingEnabledByUser());
pw.println(" isVtProvisionedOnDevice = " + isVtProvisionedOnDevice());
pw.println(" isWfcProvisionedOnDevice = " + isWfcProvisionedOnDevice());
pw.flush();
}
}