3741 lines
155 KiB
Java
3741 lines
155 KiB
Java
/*
|
|
* 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 static android.telephony.ims.ProvisioningManager.KEY_VOIMS_OPT_IN_STATUS;
|
|
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
|
|
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
|
|
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
|
|
import static android.telephony.ims.feature.RcsFeature.RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
|
|
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
|
|
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.app.PendingIntent;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.pm.PackageManager;
|
|
import android.os.Build;
|
|
import android.os.Message;
|
|
import android.os.PersistableBundle;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceSpecificException;
|
|
import android.os.SystemProperties;
|
|
import android.provider.Settings;
|
|
import android.telecom.TelecomManager;
|
|
import android.telephony.AccessNetworkConstants;
|
|
import android.telephony.BinderCacheManager;
|
|
import android.telephony.CarrierConfigManager;
|
|
import android.telephony.ServiceState;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.telephony.TelephonyFrameworkInitializer;
|
|
import android.telephony.TelephonyManager;
|
|
import android.telephony.ims.ImsCallProfile;
|
|
import android.telephony.ims.ImsCallSession;
|
|
import android.telephony.ims.ImsMmTelManager;
|
|
import android.telephony.ims.ImsReasonInfo;
|
|
import android.telephony.ims.ImsService;
|
|
import android.telephony.ims.MediaQualityStatus;
|
|
import android.telephony.ims.MediaThreshold;
|
|
import android.telephony.ims.ProvisioningManager;
|
|
import android.telephony.ims.RegistrationManager;
|
|
import android.telephony.ims.RtpHeaderExtensionType;
|
|
import android.telephony.ims.SrvccCall;
|
|
import android.telephony.ims.aidl.IImsCapabilityCallback;
|
|
import android.telephony.ims.aidl.IImsConfig;
|
|
import android.telephony.ims.aidl.IImsConfigCallback;
|
|
import android.telephony.ims.aidl.IImsMmTelFeature;
|
|
import android.telephony.ims.aidl.IImsRegistration;
|
|
import android.telephony.ims.aidl.IImsRegistrationCallback;
|
|
import android.telephony.ims.aidl.IImsSmsListener;
|
|
import android.telephony.ims.aidl.ISipTransport;
|
|
import android.telephony.ims.aidl.ISrvccStartedCallback;
|
|
import android.telephony.ims.feature.CapabilityChangeRequest;
|
|
import android.telephony.ims.feature.ImsFeature;
|
|
import android.telephony.ims.feature.MmTelFeature;
|
|
import android.telephony.ims.stub.ImsCallSessionImplBase;
|
|
import android.telephony.ims.stub.ImsRegistrationImplBase;
|
|
import android.util.SparseArray;
|
|
|
|
import com.android.ims.internal.IImsCallSession;
|
|
import com.android.ims.internal.IImsServiceFeatureCallback;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.telephony.flags.Flags;
|
|
import com.android.internal.telephony.ITelephony;
|
|
import com.android.telephony.Rlog;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Set;
|
|
import java.util.concurrent.BlockingQueue;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.LinkedBlockingDeque;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import java.util.function.Consumer;
|
|
|
|
/**
|
|
* Provides APIs for MMTEL 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 MMTEL actions.
|
|
* You can acquire an instance of it with {@link #getInstance getInstance()}.
|
|
* {Use {@link RcsFeatureManager} for RCS services}.
|
|
* For internal use ONLY! Use {@link ImsMmTelManager} instead.
|
|
* @hide
|
|
*/
|
|
public class ImsManager implements FeatureUpdates {
|
|
|
|
/*
|
|
* 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. No longer used, see
|
|
* {@link ImsCallSessionImplBase#getCallId()}.
|
|
* @deprecated Not used in the framework, keeping around symbol to not break old vendor
|
|
* components.
|
|
*/
|
|
@Deprecated
|
|
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
|
|
* @deprecated use {@link android.telephony.ims.ImsManager#ACTION_WFC_IMS_REGISTRATION_ERROR}
|
|
* instead.
|
|
*/
|
|
@Deprecated
|
|
public static final String ACTION_IMS_REGISTRATION_ERROR =
|
|
android.telephony.ims.ImsManager.ACTION_WFC_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
|
|
* @deprecated Not used in the system, keeping around to not break old vendor components.
|
|
*/
|
|
@Deprecated
|
|
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.
|
|
* @deprecated Keeping around to not break old vendor components. Use
|
|
* {@link MmTelFeature#EXTRA_IS_USSD} instead.
|
|
* @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.
|
|
* @deprecated Keeping around to not break old vendor components. Use
|
|
* {@link MmTelFeature#EXTRA_IS_UNKNOWN_CALL} instead.
|
|
* @hide
|
|
*/
|
|
public static final String EXTRA_IS_UNKNOWN_CALL = "android:isUnknown";
|
|
|
|
private static final int SUBINFO_PROPERTY_FALSE = 0;
|
|
|
|
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;
|
|
|
|
private static final int RESPONSE_WAIT_TIME_MS = 3000;
|
|
|
|
private static final int[] LOCAL_IMS_CONFIG_KEYS = {
|
|
KEY_VOIMS_OPT_IN_STATUS
|
|
};
|
|
|
|
/**
|
|
* Create a Lazy Executor that is not instantiated for this instance unless it is used. This
|
|
* is to stop threads from being started on ImsManagers that are created to do simple tasks.
|
|
*/
|
|
private static class LazyExecutor implements Executor {
|
|
private Executor mExecutor;
|
|
|
|
@Override
|
|
public void execute(Runnable runnable) {
|
|
startExecutorIfNeeded();
|
|
mExecutor.execute(runnable);
|
|
}
|
|
|
|
private synchronized void startExecutorIfNeeded() {
|
|
if (mExecutor != null) return;
|
|
mExecutor = Executors.newSingleThreadExecutor();
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public interface MmTelFeatureConnectionFactory {
|
|
MmTelFeatureConnection create(Context context, int phoneId, int subId,
|
|
IImsMmTelFeature feature, IImsConfig c, IImsRegistration r, ISipTransport s);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public interface SettingsProxy {
|
|
/** @see Settings.Secure#getInt(ContentResolver, String, int) */
|
|
int getSecureIntSetting(ContentResolver cr, String name, int def);
|
|
/** @see Settings.Secure#putInt(ContentResolver, String, int) */
|
|
boolean putSecureIntSetting(ContentResolver cr, String name, int value);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public interface SubscriptionManagerProxy {
|
|
boolean isValidSubscriptionId(int subId);
|
|
int getSubscriptionId(int slotIndex);
|
|
int getDefaultVoicePhoneId();
|
|
int getIntegerSubscriptionProperty(int subId, String propKey, int defValue);
|
|
void setSubscriptionProperty(int subId, String propKey, String propValue);
|
|
int[] getActiveSubscriptionIdList();
|
|
}
|
|
|
|
// Default implementations, which is mocked for testing
|
|
private static class DefaultSettingsProxy implements SettingsProxy {
|
|
@Override
|
|
public int getSecureIntSetting(ContentResolver cr, String name, int def) {
|
|
return Settings.Secure.getInt(cr, name, def);
|
|
}
|
|
|
|
@Override
|
|
public boolean putSecureIntSetting(ContentResolver cr, String name, int value) {
|
|
return Settings.Secure.putInt(cr, name, value);
|
|
}
|
|
}
|
|
|
|
// Default implementation which is mocked to make static dependency validation easier.
|
|
private static class DefaultSubscriptionManagerProxy implements SubscriptionManagerProxy {
|
|
|
|
private Context mContext;
|
|
|
|
public DefaultSubscriptionManagerProxy(Context context) {
|
|
mContext = context;
|
|
}
|
|
|
|
@Override
|
|
public boolean isValidSubscriptionId(int subId) {
|
|
return SubscriptionManager.isValidSubscriptionId(subId);
|
|
}
|
|
|
|
@Override
|
|
public int getSubscriptionId(int slotIndex) {
|
|
return SubscriptionManager.getSubscriptionId(slotIndex);
|
|
}
|
|
|
|
@Override
|
|
public int getDefaultVoicePhoneId() {
|
|
return SubscriptionManager.getDefaultVoicePhoneId();
|
|
}
|
|
|
|
@Override
|
|
public int getIntegerSubscriptionProperty(int subId, String propKey, int defValue) {
|
|
return SubscriptionManager.getIntegerSubscriptionProperty(subId, propKey, defValue,
|
|
mContext);
|
|
}
|
|
|
|
@Override
|
|
public void setSubscriptionProperty(int subId, String propKey, String propValue) {
|
|
SubscriptionManager.setSubscriptionProperty(subId, propKey, propValue);
|
|
}
|
|
|
|
@Override
|
|
public int[] getActiveSubscriptionIdList() {
|
|
return getSubscriptionManager().getActiveSubscriptionIdList();
|
|
}
|
|
|
|
private SubscriptionManager getSubscriptionManager() {
|
|
return mContext.getSystemService(SubscriptionManager.class);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Events that will be triggered as part of metrics collection.
|
|
*/
|
|
public interface ImsStatsCallback {
|
|
/**
|
|
* The MmTel capabilities that are enabled have changed.
|
|
* @param capability The MmTel capability
|
|
* @param regTech The IMS registration technology associated with the capability.
|
|
* @param isEnabled {@code true} if the capability is enabled, {@code false} if it is
|
|
* disabled.
|
|
*/
|
|
void onEnabledMmTelCapabilitiesChanged(
|
|
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
|
|
@ImsRegistrationImplBase.ImsRegistrationTech int regTech,
|
|
boolean isEnabled);
|
|
}
|
|
|
|
/**
|
|
* Internally we will create a FeatureConnector when {@link #getInstance(Context, int)} is
|
|
* called to keep the MmTelFeatureConnection instance fresh as new SIM cards are
|
|
* inserted/removed and MmTelFeature potentially changes.
|
|
* <p>
|
|
* For efficiency purposes, there is only one ImsManager created per-slot when using
|
|
* {@link #getInstance(Context, int)} and the same instance is returned for multiple callers.
|
|
* This is due to the ImsManager being a potentially heavyweight object depending on what it is
|
|
* being used for.
|
|
*/
|
|
private static class InstanceManager implements FeatureConnector.Listener<ImsManager> {
|
|
// If this is the first time connecting, wait a small amount of time in case IMS has already
|
|
// connected. Otherwise, ImsManager will become ready when the ImsService is connected.
|
|
private static final int CONNECT_TIMEOUT_MS = 50;
|
|
|
|
private final FeatureConnector<ImsManager> mConnector;
|
|
private final ImsManager mImsManager;
|
|
|
|
private final Object mLock = new Object();
|
|
private boolean isConnectorActive = false;
|
|
private CountDownLatch mConnectedLatch;
|
|
|
|
public InstanceManager(ImsManager manager) {
|
|
mImsManager = manager;
|
|
// Set a special prefix so that logs generated by getInstance are distinguishable.
|
|
mImsManager.mLogTagPostfix = "IM";
|
|
|
|
ArrayList<Integer> readyFilter = new ArrayList<>();
|
|
readyFilter.add(ImsFeature.STATE_READY);
|
|
readyFilter.add(ImsFeature.STATE_INITIALIZING);
|
|
readyFilter.add(ImsFeature.STATE_UNAVAILABLE);
|
|
// Pass a reference of the ImsManager being managed into the connector, allowing it to
|
|
// update the internal MmTelFeatureConnection as it is being updated.
|
|
mConnector = new FeatureConnector<>(manager.mContext, manager.mPhoneId,
|
|
(c,p) -> mImsManager, "InstanceManager", readyFilter, this,
|
|
manager.getImsThreadExecutor());
|
|
}
|
|
|
|
public ImsManager getInstance() {
|
|
return mImsManager;
|
|
}
|
|
|
|
public void reconnect() {
|
|
boolean requiresReconnect = false;
|
|
synchronized (mLock) {
|
|
if (!isConnectorActive) {
|
|
requiresReconnect = true;
|
|
isConnectorActive = true;
|
|
mConnectedLatch = new CountDownLatch(1);
|
|
}
|
|
}
|
|
if (requiresReconnect) {
|
|
mConnector.connect();
|
|
}
|
|
try {
|
|
// If this is during initial reconnect, let all threads wait for connect
|
|
// (or timeout)
|
|
if(!mConnectedLatch.await(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
|
mImsManager.log("ImsService not up yet - timeout waiting for connection.");
|
|
}
|
|
} catch (InterruptedException e) {
|
|
// Do nothing and allow ImsService to attach behind the scenes
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void connectionReady(ImsManager manager, int subId) {
|
|
synchronized (mLock) {
|
|
mImsManager.logi("connectionReady, subId: " + subId);
|
|
mConnectedLatch.countDown();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void connectionUnavailable(int reason) {
|
|
synchronized (mLock) {
|
|
mImsManager.logi("connectionUnavailable, reason: " + reason);
|
|
// only need to track the connection becoming unavailable due to telephony going
|
|
// down.
|
|
if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
|
|
isConnectorActive = false;
|
|
}
|
|
mConnectedLatch.countDown();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Replaced with single-threaded executor for testing.
|
|
private final Executor mExecutor;
|
|
// Replaced With mock for testing
|
|
private MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory =
|
|
MmTelFeatureConnection::new;
|
|
private final SubscriptionManagerProxy mSubscriptionManagerProxy;
|
|
private final SettingsProxy mSettingsProxy;
|
|
|
|
private Context mContext;
|
|
private CarrierConfigManager mConfigManager;
|
|
private int mPhoneId;
|
|
private TelephonyManager mTelephonyManager;
|
|
private AtomicReference<MmTelFeatureConnection> mMmTelConnectionRef = new AtomicReference<>();
|
|
// Used for debug purposes only currently
|
|
private boolean mConfigUpdated = false;
|
|
private BinderCacheManager<ITelephony> mBinderCache;
|
|
private ImsConfigListener mImsConfigListener;
|
|
|
|
public static final String TRUE = "true";
|
|
public static final String FALSE = "false";
|
|
// Map of phoneId -> InstanceManager
|
|
private static final SparseArray<InstanceManager> IMS_MANAGER_INSTANCES = new SparseArray<>(2);
|
|
// Map of phoneId -> ImsStatsCallback
|
|
private static final SparseArray<ImsStatsCallback> IMS_STATS_CALLBACKS = new SparseArray<>(2);
|
|
|
|
// A log prefix added to some instances of ImsManager to make it distinguishable from others.
|
|
// - "IM" added to ImsManager for ImsManagers created using getInstance.
|
|
private String mLogTagPostfix = "";
|
|
|
|
/**
|
|
* Gets a manager instance and blocks for a limited period of time, connecting to the
|
|
* corresponding ImsService MmTelFeature if it exists.
|
|
* <p>
|
|
* If the ImsService is unavailable or becomes unavailable, the associated methods will fail and
|
|
* a new ImsManager will need to be requested. Instead, a {@link FeatureConnector} can be
|
|
* requested using {@link #getConnector}, which will notify the caller when a new ImsManager is
|
|
* available.
|
|
*
|
|
* @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
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static ImsManager getInstance(Context context, int phoneId) {
|
|
InstanceManager instanceManager;
|
|
synchronized (IMS_MANAGER_INSTANCES) {
|
|
instanceManager = IMS_MANAGER_INSTANCES.get(phoneId);
|
|
if (instanceManager == null) {
|
|
ImsManager m = new ImsManager(context, phoneId);
|
|
instanceManager = new InstanceManager(m);
|
|
IMS_MANAGER_INSTANCES.put(phoneId, instanceManager);
|
|
}
|
|
}
|
|
// If the ImsManager became disconnected for some reason, try to reconnect it now.
|
|
instanceManager.reconnect();
|
|
return instanceManager.getInstance();
|
|
}
|
|
|
|
/**
|
|
* Retrieve an FeatureConnector for ImsManager, which allows a Listener to listen for when
|
|
* the ImsManager becomes available or unavailable due to the ImsService MmTelFeature moving to
|
|
* the READY state or destroyed on a specific phone modem index.
|
|
*
|
|
* @param context The Context that will be used to connect the ImsManager.
|
|
* @param phoneId The modem phone ID that the ImsManager will be created for.
|
|
* @param logPrefix The log prefix used for debugging purposes.
|
|
* @param listener The Listener that will deliver ImsManager updates as it becomes available.
|
|
* @param executor The Executor that the Listener callbacks will be called on.
|
|
* @return A FeatureConnector instance for generating ImsManagers as the associated
|
|
* MmTelFeatures become available.
|
|
*/
|
|
public static FeatureConnector<ImsManager> getConnector(Context context,
|
|
int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener,
|
|
Executor executor) {
|
|
// Only listen for the READY state from the MmTelFeature here.
|
|
ArrayList<Integer> readyFilter = new ArrayList<>();
|
|
readyFilter.add(ImsFeature.STATE_READY);
|
|
return new FeatureConnector<>(context, phoneId, ImsManager::new, logPrefix, readyFilter,
|
|
listener, executor);
|
|
}
|
|
|
|
public static boolean isImsSupportedOnDevice(Context context) {
|
|
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
|
|
}
|
|
|
|
/**
|
|
* Sets the callback that will be called when events related to IMS metric collection occur.
|
|
* <p>
|
|
* Note: Subsequent calls to this method will replace the previous stats callback.
|
|
*/
|
|
public static void setImsStatsCallback(int phoneId, ImsStatsCallback cb) {
|
|
synchronized (IMS_STATS_CALLBACKS) {
|
|
if (cb == null) {
|
|
IMS_STATS_CALLBACKS.remove(phoneId);
|
|
} else {
|
|
IMS_STATS_CALLBACKS.put(phoneId, cb);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return the {@link ImsStatsCallback} instance associated with the provided phoneId or
|
|
* {@link null} if none currently exists.
|
|
*/
|
|
private static ImsStatsCallback getStatsCallback(int phoneId) {
|
|
synchronized (IMS_STATS_CALLBACKS) {
|
|
return IMS_STATS_CALLBACKS.get(phoneId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the user configuration of Enhanced 4G LTE Mode setting.
|
|
*
|
|
* @deprecated Doesn't support MSIM devices. Use
|
|
* {@link #isEnhanced4gLteModeSettingEnabledByUser()} instead.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static boolean isEnhanced4gLteModeSettingEnabledByUser(Context context) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isEnhanced4gLteModeSettingEnabledByUser();
|
|
}
|
|
Rlog.e(TAG, "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),
|
|
* hidden ({@link CarrierConfigManager#KEY_HIDE_ENHANCED_4G_LTE_BOOL} is true), the setting is
|
|
* not initialized, and VoIMS opt-in status disabled, this method will return default value
|
|
* specified by {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
|
|
*
|
|
* Note that even if the setting was set, it may no longer be editable. If this is the case we
|
|
* return the default value.
|
|
*/
|
|
public boolean isEnhanced4gLteModeSettingEnabledByUser() {
|
|
int setting = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
boolean onByDefault = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
|
|
boolean isUiUnEditable =
|
|
!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
|
|
|| getBooleanCarrierConfig(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL);
|
|
boolean isSettingNotInitialized = setting == SUB_PROPERTY_NOT_INITIALIZED;
|
|
|
|
// If Enhanced 4G LTE Mode is uneditable, hidden, not initialized and VoIMS opt-in disabled
|
|
// we use the default value. If VoIMS opt-in is enabled, we will always allow the user to
|
|
// change the IMS enabled setting.
|
|
if ((isUiUnEditable || isSettingNotInitialized) && !isVoImsOptInEnabled()) {
|
|
return onByDefault;
|
|
} else {
|
|
return (setting == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setEnhanced4gLteModeSetting(enabled);
|
|
}
|
|
Rlog.e(TAG, "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),
|
|
* hidden ({@link CarrierConfigManager#KEY_HIDE_ENHANCED_4G_LTE_BOOL} is true), and VoIMS opt-in
|
|
* status disabled, 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 (enabled && !isVolteProvisionedOnDevice()) {
|
|
log("setEnhanced4gLteModeSetting: Not possible to enable VoLTE due to provisioning.");
|
|
return;
|
|
}
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("setEnhanced4gLteModeSetting: invalid sub id, can not set property in " +
|
|
" siminfo db; subId=" + subId);
|
|
return;
|
|
}
|
|
// If editable=false or hidden=true, we must keep default advanced 4G mode.
|
|
boolean isUiUnEditable =
|
|
!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL) ||
|
|
getBooleanCarrierConfig(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL);
|
|
|
|
// If VoIMS opt-in is enabled, we will always allow the user to change the IMS enabled
|
|
// setting.
|
|
if (isUiUnEditable && !isVoImsOptInEnabled()) {
|
|
enabled = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
|
|
}
|
|
|
|
int prevSetting = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(subId,
|
|
SubscriptionManager.ENHANCED_4G_MODE_ENABLED, SUB_PROPERTY_NOT_INITIALIZED);
|
|
|
|
if (prevSetting == (enabled ? ProvisioningManager.PROVISIONING_VALUE_ENABLED :
|
|
ProvisioningManager.PROVISIONING_VALUE_DISABLED)) {
|
|
// No change in setting.
|
|
return;
|
|
}
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
|
|
booleanToPropertyString(enabled));
|
|
try {
|
|
if (enabled) {
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
boolean isNonTty = isNonTtyOrTtyOnVolteEnabled();
|
|
// This affects voice and video enablement
|
|
updateVoiceCellFeatureValue(request, isNonTty);
|
|
updateVideoCallFeatureValue(request, isNonTty);
|
|
changeMmTelCapability(request);
|
|
// Ensure IMS is on if this setting is enabled.
|
|
turnOnIms();
|
|
} else {
|
|
// This may trigger entire IMS interface to be disabled, so recalculate full state.
|
|
reevaluateCapabilities();
|
|
}
|
|
} catch (ImsException e) {
|
|
loge("setEnhanced4gLteModeSetting couldn't set config: " + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static boolean isNonTtyOrTtyOnVolteEnabled(Context context) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isNonTtyOrTtyOnVolteEnabled();
|
|
}
|
|
Rlog.e(TAG, "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 (isTtyOnVoLteCapable()) {
|
|
return true;
|
|
}
|
|
|
|
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
|
|
if (tm == null) {
|
|
logw("isNonTtyOrTtyOnVolteEnabled: telecom not available");
|
|
return true;
|
|
}
|
|
return tm.getCurrentTtyMode() == TelecomManager.TTY_MODE_OFF;
|
|
}
|
|
|
|
public boolean isTtyOnVoLteCapable() {
|
|
return getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL);
|
|
}
|
|
|
|
/**
|
|
* @return true if we are either not on TTY or TTY over VoWiFi is enabled. If we
|
|
* are on TTY and TTY over VoWiFi is not allowed, this method will return false.
|
|
*/
|
|
public boolean isNonTtyOrTtyOnVoWifiEnabled() {
|
|
|
|
if (isTtyOnVoWifiCapable()) {
|
|
return true;
|
|
}
|
|
|
|
TelecomManager tm = mContext.getSystemService(TelecomManager.class);
|
|
if (tm == null) {
|
|
logw("isNonTtyOrTtyOnVoWifiEnabled: telecom not available");
|
|
return true;
|
|
}
|
|
return tm.getCurrentTtyMode() == TelecomManager.TTY_MODE_OFF;
|
|
}
|
|
|
|
public boolean isTtyOnVoWifiCapable() {
|
|
return getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOWIFI_TTY_SUPPORTED_BOOL);
|
|
}
|
|
|
|
/**
|
|
* Returns a platform configuration for VoLTE which may override the user setting.
|
|
* @deprecated Does not support MSIM devices. Please use
|
|
* {@link #isVolteEnabledByPlatform()} instead.
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public static boolean isVolteEnabledByPlatform(Context context) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isVolteEnabledByPlatform();
|
|
}
|
|
Rlog.e(TAG, "isVolteEnabledByPlatform: ImsManager null, returning default value.");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Asynchronous call to ImsService to determine whether or not a specific MmTel capability is
|
|
* supported.
|
|
*/
|
|
public void isSupported(int capability, int transportType, Consumer<Boolean> result) {
|
|
getImsThreadExecutor().execute(() -> {
|
|
switch(transportType) {
|
|
// Does not take into account NR, as NR is a superset of LTE support currently.
|
|
case (AccessNetworkConstants.TRANSPORT_TYPE_WWAN): {
|
|
switch (capability) {
|
|
case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE): {
|
|
result.accept(isVolteEnabledByPlatform());
|
|
return;
|
|
} case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO): {
|
|
result.accept(isVtEnabledByPlatform());
|
|
return;
|
|
}case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT): {
|
|
result.accept(isSuppServicesOverUtEnabledByPlatform());
|
|
return;
|
|
} case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS): {
|
|
// There is currently no carrier config defined for this.
|
|
result.accept(true);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
} case (AccessNetworkConstants.TRANSPORT_TYPE_WLAN): {
|
|
switch (capability) {
|
|
case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE) : {
|
|
result.accept(isWfcEnabledByPlatform());
|
|
return;
|
|
} case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO) : {
|
|
// This is not transport dependent at this time.
|
|
result.accept(isVtEnabledByPlatform());
|
|
return;
|
|
} case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT) : {
|
|
// This is not transport dependent at this time.
|
|
result.accept(isSuppServicesOverUtEnabledByPlatform());
|
|
return;
|
|
} case (MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS) : {
|
|
// There is currently no carrier config defined for this.
|
|
result.accept(true);
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// false for unknown capability/transport types.
|
|
result.accept(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;
|
|
}
|
|
|
|
if (getLocalImsConfigKeyInt(KEY_VOIMS_OPT_IN_STATUS)
|
|
== ProvisioningManager.PROVISIONING_VALUE_ENABLED) {
|
|
return true;
|
|
}
|
|
|
|
return mContext.getResources().getBoolean(
|
|
com.android.internal.R.bool.config_device_volte_available)
|
|
&& getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
|
|
&& isGbaValid();
|
|
}
|
|
|
|
/**
|
|
* @return {@code true} if IMS over NR is enabled by the platform, {@code false} otherwise.
|
|
*/
|
|
public boolean isImsOverNrEnabledByPlatform() {
|
|
int[] nrCarrierCaps = getIntArrayCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
|
|
if (nrCarrierCaps == null) return false;
|
|
boolean voNrCarrierSupported = Arrays.stream(nrCarrierCaps)
|
|
.anyMatch(cap -> cap == CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA);
|
|
if (!voNrCarrierSupported) return false;
|
|
return 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isVolteProvisionedOnDevice();
|
|
}
|
|
Rlog.e(TAG, "isVolteProvisionedOnDevice: ImsManager null, returning default value.");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether VoLTE is provisioned on this slot.
|
|
*/
|
|
public boolean isVolteProvisionedOnDevice() {
|
|
if (isMmTelProvisioningRequired(CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE)) {
|
|
return isVolteProvisioned();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether EAB is provisioned on this slot.
|
|
*/
|
|
public boolean isEabProvisionedOnDevice() {
|
|
if (isRcsProvisioningRequired(CAPABILITY_TYPE_PRESENCE_UCE, REGISTRATION_TECH_LTE)) {
|
|
return isEabProvisioned();
|
|
}
|
|
|
|
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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isWfcProvisionedOnDevice();
|
|
}
|
|
Rlog.e(TAG, "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 (isMmTelProvisioningRequired(CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN)) {
|
|
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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isVtProvisionedOnDevice();
|
|
}
|
|
Rlog.e(TAG, "isVtProvisionedOnDevice: ImsManager null, returning default value.");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Indicates whether VT is provisioned on slot.
|
|
*/
|
|
public boolean isVtProvisionedOnDevice() {
|
|
if (isMmTelProvisioningRequired(CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE)) {
|
|
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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isVtEnabledByPlatform();
|
|
}
|
|
Rlog.e(TAG, "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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isVtEnabledByUser();
|
|
}
|
|
Rlog.e(TAG, "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 = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.VT_IMS_ENABLED,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
|
|
// If it's never set, by default we return true.
|
|
return (setting == SUB_PROPERTY_NOT_INITIALIZED
|
|
|| setting == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
|
|
}
|
|
|
|
/**
|
|
* Returns whether the user sets call composer setting per sub.
|
|
*/
|
|
public boolean isCallComposerEnabledByUser() {
|
|
if (mTelephonyManager == null) {
|
|
loge("isCallComposerEnabledByUser: TelephonyManager is null, returning false");
|
|
return false;
|
|
}
|
|
return mTelephonyManager.getCallComposerStatus()
|
|
== TelephonyManager.CALL_COMPOSER_STATUS_ON;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the business only call composer is on.
|
|
*/
|
|
public boolean isBusinessOnlyCallComposerEnabledByUser() {
|
|
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
|
|
if (tm == null) {
|
|
loge("isBusinessOnlyCallComposerEnabledByUser: TelephonyManager is null");
|
|
return false;
|
|
}
|
|
return tm.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_BUSINESS_ONLY;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setVtSetting(enabled);
|
|
}
|
|
Rlog.e(TAG, "setVtSetting: ImsManager null, can not set value.");
|
|
}
|
|
|
|
/**
|
|
* Change persistent VT enabled setting for slot.
|
|
*/
|
|
public void setVtSetting(boolean enabled) {
|
|
if (enabled && !isVtProvisionedOnDevice()) {
|
|
log("setVtSetting: Not possible to enable Vt due to provisioning.");
|
|
return;
|
|
}
|
|
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("setVtSetting: sub id invalid, skip modifying vt state in subinfo db; subId="
|
|
+ subId);
|
|
return;
|
|
}
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId, SubscriptionManager.VT_IMS_ENABLED,
|
|
booleanToPropertyString(enabled));
|
|
try {
|
|
if (enabled) {
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
updateVideoCallFeatureValue(request, isNonTtyOrTtyOnVolteEnabled());
|
|
changeMmTelCapability(request);
|
|
// ensure IMS is enabled.
|
|
turnOnIms();
|
|
} else {
|
|
// This may cause IMS to be disabled, re-evaluate all.
|
|
reevaluateCapabilities();
|
|
}
|
|
} catch (ImsException 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.
|
|
*/
|
|
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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isWfcEnabledByUser();
|
|
}
|
|
Rlog.e(TAG, "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 = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.WFC_IMS_ENABLED,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
|
|
// 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 == ProvisioningManager.PROVISIONING_VALUE_ENABLED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change persistent WFC enabled setting.
|
|
* @deprecated Does not support MSIM devices. Please use
|
|
* {@link #setWfcSetting} instead.
|
|
*/
|
|
public static void setWfcSetting(Context context, boolean enabled) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setWfcSetting(enabled);
|
|
}
|
|
Rlog.e(TAG, "setWfcSetting: ImsManager null, can not set value.");
|
|
}
|
|
|
|
/**
|
|
* Change persistent WFC enabled setting for slot.
|
|
*/
|
|
public void setWfcSetting(boolean enabled) {
|
|
if (enabled && !isWfcProvisionedOnDevice()) {
|
|
log("setWfcSetting: Not possible to enable WFC due to provisioning.");
|
|
return;
|
|
}
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("setWfcSetting: invalid sub id, can not set WFC setting in siminfo db; subId="
|
|
+ subId);
|
|
return;
|
|
}
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_ENABLED, booleanToPropertyString(enabled));
|
|
|
|
try {
|
|
if (enabled) {
|
|
boolean isNonTtyWifi = isNonTtyOrTtyOnVoWifiEnabled();
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
updateVoiceWifiFeatureAndProvisionedValues(request, isNonTtyWifi);
|
|
changeMmTelCapability(request);
|
|
// Ensure IMS is on if this setting is updated.
|
|
turnOnIms();
|
|
} else {
|
|
// This may cause IMS to be disabled, re-evaluate all caps
|
|
reevaluateCapabilities();
|
|
}
|
|
} catch (ImsException e) {
|
|
loge("setWfcSetting: " + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true if the user's setting for Voice over Cross SIM is enabled and
|
|
* false if it is not
|
|
*/
|
|
public boolean isCrossSimCallingEnabledByUser() {
|
|
int setting = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.CROSS_SIM_CALLING_ENABLED,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
|
|
// SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
|
|
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
|
|
return false;
|
|
} else {
|
|
return setting == ProvisioningManager.PROVISIONING_VALUE_ENABLED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true if Voice over Cross SIM is provisioned and enabled by user and platform.
|
|
* false if any of them is not true
|
|
*/
|
|
public boolean isCrossSimCallingEnabled() {
|
|
boolean userEnabled = isCrossSimCallingEnabledByUser();
|
|
boolean platformEnabled = isCrossSimEnabledByPlatform();
|
|
boolean isProvisioned = isWfcProvisionedOnDevice();
|
|
|
|
log("isCrossSimCallingEnabled: platformEnabled = " + platformEnabled
|
|
+ ", provisioned = " + isProvisioned
|
|
+ ", userEnabled = " + userEnabled);
|
|
return userEnabled && platformEnabled && isProvisioned;
|
|
}
|
|
|
|
/**
|
|
* Sets the user's setting for whether or not Voice over Cross SIM is enabled.
|
|
*/
|
|
public void setCrossSimCallingEnabled(boolean enabled) {
|
|
if (enabled && !isWfcProvisionedOnDevice()) {
|
|
log("setCrossSimCallingEnabled: Not possible to enable WFC due to provisioning.");
|
|
return;
|
|
}
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("setCrossSimCallingEnabled: "
|
|
+ "invalid sub id, can not set Cross SIM setting in siminfo db; subId="
|
|
+ subId);
|
|
return;
|
|
}
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.CROSS_SIM_CALLING_ENABLED, booleanToPropertyString(enabled));
|
|
try {
|
|
if (enabled) {
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
updateCrossSimFeatureAndProvisionedValues(request);
|
|
changeMmTelCapability(request);
|
|
turnOnIms();
|
|
} else {
|
|
// Recalculate all caps to determine if IMS needs to be disabled.
|
|
reevaluateCapabilities();
|
|
}
|
|
} catch (ImsException e) {
|
|
loge("setCrossSimCallingEnabled(): ", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Non-persistently change WFC enabled setting and WFC mode for slot
|
|
*
|
|
* @param enabled If true, WFC and WFC while roaming will be enabled for the associated
|
|
* subscription, if supported by the carrier. If false, WFC will be disabled for
|
|
* the associated subscription.
|
|
* @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 : ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED;
|
|
try {
|
|
changeMmTelCapability(enabled, MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
|
|
// Set the mode and roaming enabled settings before turning on IMS
|
|
setWfcModeInternal(imsWfcModeFeatureValue);
|
|
// If enabled is false, shortcut to false because of the ImsService
|
|
// implementation for WFC roaming, otherwise use the correct user's setting.
|
|
setWfcRoamingSettingInternal(enabled && isWfcRoamingEnabledByUser());
|
|
// Do not re-evaluate all capabilities because this is a temporary override of WFC
|
|
// settings.
|
|
if (enabled) {
|
|
log("setWfcNonPersistent() : turnOnIms");
|
|
// Ensure IMS is turned on if this is enabled.
|
|
turnOnIms();
|
|
}
|
|
} catch (ImsException e) {
|
|
loge("setWfcNonPersistent(): ", 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.getWfcMode();
|
|
}
|
|
Rlog.e(TAG, "getWfcMode: ImsManager null, returning default value.");
|
|
return ImsMmTelManager.WIFI_MODE_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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setWfcMode(wfcMode);
|
|
}
|
|
Rlog.e(TAG, "setWfcMode: ImsManager null, can not set value.");
|
|
}
|
|
|
|
/**
|
|
* Change persistent WFC preference setting for slot when not roaming.
|
|
* @deprecated Use {@link #setWfcMode(int, boolean)} instead.
|
|
*/
|
|
public void setWfcMode(int wfcMode) {
|
|
setWfcMode(wfcMode, false /*isRoaming*/);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.getWfcMode(roaming);
|
|
}
|
|
Rlog.e(TAG, "getWfcMode: ImsManager null, returning default value.");
|
|
return ImsMmTelManager.WIFI_MODE_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;
|
|
if (!roaming) {
|
|
// The WFC mode is not editable, return the default setting in the CarrierConfig, not
|
|
// the user set value.
|
|
if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL)) {
|
|
setting = getIntCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT);
|
|
|
|
} else {
|
|
setting = getSettingFromSubscriptionManager(SubscriptionManager.WFC_IMS_MODE,
|
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT);
|
|
}
|
|
if (DBG) log("getWfcMode - setting=" + setting);
|
|
} else {
|
|
if (getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)) {
|
|
setting = getWfcMode(false);
|
|
} else if (overrideWfcRoamingModeWhileUsingNTN()) {
|
|
if (DBG) log("getWfcMode (roaming) "
|
|
+ "- override Wfc roaming mode to WIFI_PREFERRED");
|
|
setting = ImsConfig.WfcModeFeatureValueConstants.WIFI_PREFERRED;
|
|
} else if (!getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL)) {
|
|
setting = getIntCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT);
|
|
} else {
|
|
setting = getSettingFromSubscriptionManager(
|
|
SubscriptionManager.WFC_IMS_ROAMING_MODE,
|
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT);
|
|
}
|
|
if (DBG) log("getWfcMode (roaming) - setting=" + setting);
|
|
}
|
|
return setting;
|
|
}
|
|
|
|
/**
|
|
* Returns the SubscriptionManager setting for the subSetting string. If it is not set, default
|
|
* to the default CarrierConfig value for defaultConfigKey.
|
|
*/
|
|
private int getSettingFromSubscriptionManager(String subSetting, String defaultConfigKey) {
|
|
int result;
|
|
result = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(getSubId(), subSetting,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
|
|
// SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
|
|
if (result == SUB_PROPERTY_NOT_INITIALIZED) {
|
|
result = getIntCarrierConfig(defaultConfigKey);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setWfcMode(wfcMode, roaming);
|
|
}
|
|
Rlog.e(TAG, "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) {
|
|
int subId = getSubId();
|
|
if (isSubIdValid(subId)) {
|
|
if (!roaming) {
|
|
if (DBG) log("setWfcMode(i,b) - setting=" + wfcMode);
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId, SubscriptionManager.WFC_IMS_MODE,
|
|
Integer.toString(wfcMode));
|
|
} else {
|
|
if (DBG) log("setWfcMode(i,b) (roaming) - setting=" + wfcMode);
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_ROAMING_MODE, Integer.toString(wfcMode));
|
|
}
|
|
} else {
|
|
loge("setWfcMode(i,b): invalid sub id, skip setting setting in siminfo db; subId="
|
|
+ subId);
|
|
}
|
|
|
|
if (mTelephonyManager == null) {
|
|
loge("setWfcMode: TelephonyManager is null, can not set WFC.");
|
|
return;
|
|
}
|
|
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(getSubId());
|
|
// Unfortunately, the WFC mode is the same for home/roaming (we do not have separate
|
|
// config keys), so we have to change the WFC mode when moving home<->roaming. So, only
|
|
// call setWfcModeInternal when roaming == telephony roaming status. Otherwise, ignore.
|
|
if (roaming == tm.isNetworkRoaming()) {
|
|
setWfcModeInternal(wfcMode);
|
|
}
|
|
}
|
|
|
|
private int getSubId() {
|
|
return mSubscriptionManagerProxy.getSubscriptionId(mPhoneId);
|
|
}
|
|
|
|
private void setWfcModeInternal(int wfcMode) {
|
|
final int value = wfcMode;
|
|
getImsThreadExecutor().execute(() -> {
|
|
try {
|
|
getConfigInterface().setConfig(
|
|
ProvisioningManager.KEY_VOICE_OVER_WIFI_MODE_OVERRIDE, value);
|
|
} catch (ImsException e) {
|
|
// do nothing
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isWfcRoamingEnabledByUser();
|
|
}
|
|
Rlog.e(TAG, "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 = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
|
|
return getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
|
|
} else {
|
|
return setting == ProvisioningManager.PROVISIONING_VALUE_ENABLED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change persistent WFC roaming enabled setting
|
|
*/
|
|
public static void setWfcRoamingSetting(Context context, boolean enabled) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.setWfcRoamingSetting(enabled);
|
|
}
|
|
Rlog.e(TAG, "setWfcRoamingSetting: ImsManager null, value not set.");
|
|
}
|
|
|
|
/**
|
|
* Change persistent WFC roaming enabled setting
|
|
*/
|
|
public void setWfcRoamingSetting(boolean enabled) {
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(getSubId(),
|
|
SubscriptionManager.WFC_IMS_ROAMING_ENABLED, booleanToPropertyString(enabled)
|
|
);
|
|
|
|
setWfcRoamingSettingInternal(enabled);
|
|
}
|
|
|
|
private void setWfcRoamingSettingInternal(boolean enabled) {
|
|
final int value = enabled
|
|
? ProvisioningManager.PROVISIONING_VALUE_ENABLED
|
|
: ProvisioningManager.PROVISIONING_VALUE_DISABLED;
|
|
getImsThreadExecutor().execute(() -> {
|
|
try {
|
|
getConfigInterface().setConfig(
|
|
ProvisioningManager.KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE, value);
|
|
} catch (ImsException e) {
|
|
// do nothing
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
return mgr.isWfcEnabledByPlatform();
|
|
}
|
|
Rlog.e(TAG, "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();
|
|
}
|
|
|
|
/**
|
|
* Returns a platform configuration for Cross SIM which may override the user
|
|
* setting per slot. Note: Cross SIM presumes that VoLTE is enabled (these are
|
|
* configuration settings which must be done correctly).
|
|
*/
|
|
public boolean isCrossSimEnabledByPlatform() {
|
|
if (isWfcEnabledByPlatform()) {
|
|
return getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean isSuppServicesOverUtEnabledByPlatform() {
|
|
int cardState = mTelephonyManager.getSimState(mPhoneId);
|
|
if (cardState != TelephonyManager.SIM_STATE_READY) {
|
|
// Do not report enabled until we actually have an active subscription.
|
|
return false;
|
|
}
|
|
return getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_SUPPORTS_SS_OVER_UT_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)) {
|
|
if (mTelephonyManager == null) {
|
|
loge("isGbaValid: TelephonyManager is null, returning false.");
|
|
return false;
|
|
}
|
|
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(getSubId());
|
|
String efIst = tm.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 MmTel config value or return false if we receive an error from the AOSP
|
|
* storage(ImsProvisioningController) implementation for that value.
|
|
*/
|
|
private boolean getImsProvisionedBoolNoException(int capability, int tech) {
|
|
int subId = getSubId();
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
logw("getImsProvisionedBoolNoException subId is invalid");
|
|
return false;
|
|
}
|
|
|
|
ITelephony iTelephony = getITelephony();
|
|
if (iTelephony == null) {
|
|
logw("getImsProvisionedBoolNoException ITelephony interface is invalid");
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return iTelephony.getImsProvisioningStatusForCapability(subId, capability, tech);
|
|
} catch (RemoteException | IllegalArgumentException e) {
|
|
logw("getImsProvisionedBoolNoException: operation failed for capability=" + capability
|
|
+ ". Exception:" + e.getMessage() + ". Returning false.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Will return with Rcs config value or return false if we receive an error from the AOSP
|
|
* storage(ImsProvisioningController) implementation for that value.
|
|
*/
|
|
private boolean getRcsProvisionedBoolNoException(int capability, int tech) {
|
|
int subId = getSubId();
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
logw("getRcsProvisionedBoolNoException subId is invalid");
|
|
return false;
|
|
}
|
|
|
|
ITelephony iTelephony = getITelephony();
|
|
if (iTelephony == null) {
|
|
logw("getRcsProvisionedBoolNoException ITelephony interface is invalid");
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return iTelephony.getRcsProvisioningStatusForCapability(subId, capability, tech);
|
|
} catch (RemoteException | IllegalArgumentException e) {
|
|
logw("getRcsProvisionedBoolNoException: operation failed for capability=" + capability
|
|
+ ". Exception:" + e.getMessage() + ". Returning false.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Push configuration updates to the ImsService implementation.
|
|
*/
|
|
public void updateImsServiceConfig() {
|
|
try {
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("updateImsServiceConfig: invalid sub id, skipping!");
|
|
return;
|
|
}
|
|
PersistableBundle imsCarrierConfigs =
|
|
mConfigManager.getConfigByComponentForSubId(
|
|
CarrierConfigManager.Ims.KEY_PREFIX, subId);
|
|
updateImsCarrierConfigs(imsCarrierConfigs);
|
|
reevaluateCapabilities();
|
|
mConfigUpdated = true;
|
|
} catch (ImsException e) {
|
|
loge("updateImsServiceConfig: ", e);
|
|
mConfigUpdated = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evaluate the state of the IMS capabilities and push the updated state to the ImsService.
|
|
*/
|
|
private void reevaluateCapabilities() throws ImsException {
|
|
logi("reevaluateCapabilities");
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
boolean isNonTty = isNonTtyOrTtyOnVolteEnabled();
|
|
boolean isNonTtyWifi = isNonTtyOrTtyOnVoWifiEnabled();
|
|
updateVoiceCellFeatureValue(request, isNonTty);
|
|
updateVoiceWifiFeatureAndProvisionedValues(request, isNonTtyWifi);
|
|
updateCrossSimFeatureAndProvisionedValues(request);
|
|
updateVideoCallFeatureValue(request, isNonTty);
|
|
if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
|
|
updateCallComposerFeatureValue(request);
|
|
} else {
|
|
updateCallComposerFeatureValueLegacy(request);
|
|
}
|
|
// Only turn on IMS for RTT if there's an active subscription present. If not, the
|
|
// modem will be in emergency-call-only mode and will use separate signaling to
|
|
// establish an RTT emergency call.
|
|
boolean isImsNeededForRtt = updateRttConfigValue() && isActiveSubscriptionPresent();
|
|
// Supplementary services over UT do not require IMS registration. Do not alter IMS
|
|
// registration based on UT.
|
|
updateUtFeatureValue(request);
|
|
|
|
// Send the batched request to the modem.
|
|
changeMmTelCapability(request);
|
|
|
|
if (isImsNeededForRtt || !isTurnOffImsAllowedByPlatform() || isImsNeeded(request)) {
|
|
// 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("reevaluateCapabilities: turnOnIms");
|
|
turnOnIms();
|
|
} else {
|
|
// Turn off IMS if it is not used AND turning off is allowed for carrier.
|
|
log("reevaluateCapabilities: turnOffIms");
|
|
turnOffIms();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {@code true} if IMS needs to be turned on for the request, {@code false} if it can
|
|
* be disabled.
|
|
*/
|
|
private boolean isImsNeeded(CapabilityChangeRequest r) {
|
|
return r.getCapabilitiesToEnable().stream()
|
|
.anyMatch(c -> isImsNeededForCapability(c.getCapability()));
|
|
}
|
|
|
|
/**
|
|
* @return {@code true} if IMS needs to be turned on for the capability.
|
|
*/
|
|
private boolean isImsNeededForCapability(int capability) {
|
|
if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
|
|
// UT does not require IMS to be enabled.
|
|
return capability != MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT &&
|
|
// call composer is used as part of calling, so it should not trigger the
|
|
// enablement
|
|
// of IMS.
|
|
capability != MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER &&
|
|
capability != MmTelFeature.MmTelCapabilities
|
|
.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY;
|
|
} else {
|
|
// UT does not require IMS to be enabled.
|
|
return capability != MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT &&
|
|
// call composer is used as part of calling, so it should not trigger the
|
|
// enablement
|
|
// of IMS.
|
|
capability != MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update VoLTE config
|
|
*/
|
|
private void updateVoiceCellFeatureValue(CapabilityChangeRequest request, boolean isNonTty) {
|
|
boolean available = isVolteEnabledByPlatform();
|
|
boolean enabled = isEnhanced4gLteModeSettingEnabledByUser();
|
|
boolean isProvisioned = isVolteProvisionedOnDevice();
|
|
boolean voLteFeatureOn = available && enabled && isNonTty && isProvisioned;
|
|
boolean voNrAvailable = isImsOverNrEnabledByPlatform();
|
|
|
|
log("updateVoiceCellFeatureValue: available = " + available
|
|
+ ", enabled = " + enabled
|
|
+ ", nonTTY = " + isNonTty
|
|
+ ", provisioned = " + isProvisioned
|
|
+ ", voLteFeatureOn = " + voLteFeatureOn
|
|
+ ", voNrAvailable = " + voNrAvailable);
|
|
|
|
if (voLteFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
}
|
|
if (voLteFeatureOn && voNrAvailable) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update video call configuration
|
|
*/
|
|
private void updateVideoCallFeatureValue(CapabilityChangeRequest request, boolean isNonTty) {
|
|
boolean available = isVtEnabledByPlatform();
|
|
boolean vtEnabled = isVtEnabledByUser();
|
|
boolean advancedEnabled = isEnhanced4gLteModeSettingEnabledByUser();
|
|
boolean isDataEnabled = isDataEnabled();
|
|
boolean ignoreDataEnabledChanged = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS);
|
|
boolean isProvisioned = isVtProvisionedOnDevice();
|
|
// TODO: Support carrier config setting about if VT settings should be associated with
|
|
// advanced calling settings.
|
|
boolean isLteFeatureOn = available && vtEnabled && isNonTty && isProvisioned
|
|
&& advancedEnabled && (ignoreDataEnabledChanged || isDataEnabled);
|
|
boolean nrAvailable = isImsOverNrEnabledByPlatform();
|
|
|
|
log("updateVideoCallFeatureValue: available = " + available
|
|
+ ", vtenabled = " + vtEnabled
|
|
+ ", advancedCallEnabled = " + advancedEnabled
|
|
+ ", nonTTY = " + isNonTty
|
|
+ ", data enabled = " + isDataEnabled
|
|
+ ", provisioned = " + isProvisioned
|
|
+ ", isLteFeatureOn = " + isLteFeatureOn
|
|
+ ", nrAvailable = " + nrAvailable);
|
|
|
|
if (isLteFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
// VT does not differentiate transport today, do not set IWLAN.
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
// VT does not differentiate transport today, do not set IWLAN.
|
|
}
|
|
|
|
if (isLteFeatureOn && nrAvailable) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update WFC config
|
|
*/
|
|
private void updateVoiceWifiFeatureAndProvisionedValues(CapabilityChangeRequest request,
|
|
boolean isNonTty) {
|
|
boolean isNetworkRoaming = false;
|
|
if (mTelephonyManager == null) {
|
|
loge("updateVoiceWifiFeatureAndProvisionedValues: TelephonyManager is null, assuming"
|
|
+ " not roaming.");
|
|
} else {
|
|
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(getSubId());
|
|
isNetworkRoaming = tm.isNetworkRoaming();
|
|
}
|
|
|
|
boolean available = isWfcEnabledByPlatform();
|
|
boolean enabled = isWfcEnabledByUser();
|
|
boolean isProvisioned = isWfcProvisionedOnDevice();
|
|
int mode = getWfcMode(isNetworkRoaming);
|
|
boolean roaming = isWfcRoamingEnabledByUser();
|
|
boolean isFeatureOn = available && enabled && isProvisioned;
|
|
|
|
log("updateWfcFeatureAndProvisionedValues: available = " + available
|
|
+ ", enabled = " + enabled
|
|
+ ", mode = " + mode
|
|
+ ", provisioned = " + isProvisioned
|
|
+ ", roaming = " + roaming
|
|
+ ", isFeatureOn = " + isFeatureOn
|
|
+ ", isNonTtyWifi = " + isNonTty);
|
|
|
|
if (isFeatureOn && isNonTty) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
|
|
}
|
|
|
|
if (!isFeatureOn) {
|
|
mode = ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED;
|
|
roaming = false;
|
|
}
|
|
setWfcModeInternal(mode);
|
|
setWfcRoamingSettingInternal(roaming);
|
|
}
|
|
|
|
/**
|
|
* Update Cross SIM config
|
|
*/
|
|
private void updateCrossSimFeatureAndProvisionedValues(CapabilityChangeRequest request) {
|
|
if (isCrossSimCallingEnabled()) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM);
|
|
}
|
|
}
|
|
|
|
|
|
private void updateUtFeatureValue(CapabilityChangeRequest request) {
|
|
boolean isCarrierSupported = isSuppServicesOverUtEnabledByPlatform();
|
|
|
|
// check new carrier config first KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
|
|
// if that returns false, check deprecated carrier config
|
|
// KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
|
|
boolean requiresProvisioning = isMmTelProvisioningRequired(CAPABILITY_TYPE_UT,
|
|
REGISTRATION_TECH_LTE) || getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL);
|
|
// Count as "provisioned" if we do not require provisioning.
|
|
boolean isProvisioned = true;
|
|
if (requiresProvisioning) {
|
|
ITelephony telephony = getITelephony();
|
|
// Only track UT over LTE, since we do not differentiate between UT over LTE and IWLAN
|
|
// currently.
|
|
try {
|
|
if (telephony != null) {
|
|
isProvisioned = telephony.getImsProvisioningStatusForCapability(getSubId(),
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
}
|
|
} catch (RemoteException e) {
|
|
loge("updateUtFeatureValue: couldn't reach telephony! returning provisioned");
|
|
}
|
|
}
|
|
boolean isFeatureOn = isCarrierSupported && isProvisioned;
|
|
|
|
log("updateUtFeatureValue: available = " + isCarrierSupported
|
|
+ ", isProvisioned = " + isProvisioned
|
|
+ ", enabled = " + isFeatureOn);
|
|
|
|
if (isFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update call composer capability
|
|
*/
|
|
private void updateCallComposerFeatureValueLegacy(CapabilityChangeRequest request) {
|
|
boolean isUserSetEnabled = isCallComposerEnabledByUser();
|
|
boolean isCarrierConfigEnabled = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_SUPPORTS_CALL_COMPOSER_BOOL);
|
|
|
|
boolean isFeatureOn = isUserSetEnabled && isCarrierConfigEnabled;
|
|
boolean nrAvailable = isImsOverNrEnabledByPlatform();
|
|
|
|
log("updateCallComposerFeatureValue: isUserSetEnabled = " + isUserSetEnabled
|
|
+ ", isCarrierConfigEnabled = " + isCarrierConfigEnabled
|
|
+ ", isFeatureOn = " + isFeatureOn
|
|
+ ", nrAvailable = " + nrAvailable);
|
|
|
|
if (isFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
}
|
|
if (isFeatureOn && nrAvailable) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update call composer capability
|
|
*/
|
|
private void updateCallComposerFeatureValue(CapabilityChangeRequest request) {
|
|
// query user set values
|
|
boolean isCallComposerEnabledByUser = isCallComposerEnabledByUser();
|
|
boolean isBusinessComposerEnabledByUser = isBusinessOnlyCallComposerEnabledByUser();
|
|
// query carrier set values
|
|
boolean isCallComposerEnabledByConfig = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_SUPPORTS_CALL_COMPOSER_BOOL);
|
|
boolean isBusinessComposerEnabledByConfig = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
|
|
// determine feature settings
|
|
boolean isCallComposerFeatureOn = isCallComposerEnabledByUser
|
|
&& isCallComposerEnabledByConfig;
|
|
boolean isBusinessOnlyComposerFeatureOn = isBusinessComposerEnabledByUser
|
|
&& isBusinessComposerEnabledByConfig;
|
|
|
|
boolean nrAvailable = isImsOverNrEnabledByPlatform();
|
|
|
|
logi("updateCallComposerFeatureValue:"
|
|
+ " isCallComposerEnabledByUser = " + isCallComposerEnabledByUser
|
|
+ ", isCallComposerEnabledByConfig = " + isCallComposerEnabledByConfig
|
|
+ ", isCallComposerFeatureOn = " + isCallComposerFeatureOn
|
|
+ ", isBusinessOnlyComposerFeatureOn = " + isBusinessOnlyComposerFeatureOn
|
|
+ ", isBusinessComposerEnabledByUser = " + isBusinessComposerEnabledByUser
|
|
+ ", isBusinessComposerEnabledByConfig = " + isBusinessComposerEnabledByConfig
|
|
+ ", nrAvailable = " + nrAvailable);
|
|
|
|
// enable/disable composers for LTE
|
|
if (isCallComposerFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
} else if (isBusinessOnlyComposerFeatureOn) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
|
|
}
|
|
|
|
// enable/disable composers for NR
|
|
if (isCallComposerFeatureOn && nrAvailable) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
} else if (isBusinessOnlyComposerFeatureOn && nrAvailable) {
|
|
request.addCapabilitiesToEnableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
} else {
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
request.addCapabilitiesToDisableForTech(
|
|
MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY,
|
|
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do NOT use this directly, instead use {@link #getInstance(Context, int)}.
|
|
*/
|
|
private ImsManager(Context context, int phoneId) {
|
|
mContext = context;
|
|
mPhoneId = phoneId;
|
|
mTelephonyManager = context.getSystemService(TelephonyManager.class);
|
|
mSubscriptionManagerProxy = new DefaultSubscriptionManagerProxy(context);
|
|
mSettingsProxy = new DefaultSettingsProxy();
|
|
mConfigManager = (CarrierConfigManager) context.getSystemService(
|
|
Context.CARRIER_CONFIG_SERVICE);
|
|
mExecutor = new LazyExecutor();
|
|
mBinderCache = new BinderCacheManager<>(ImsManager::getITelephonyInterface);
|
|
// Start off with an empty MmTelFeatureConnection, which will be replaced one an
|
|
// ImsService is available (ImsManager expects a non-null FeatureConnection)
|
|
associate(null /*container*/, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
}
|
|
|
|
/**
|
|
* Used for testing only to inject dependencies.
|
|
*/
|
|
@VisibleForTesting
|
|
public ImsManager(Context context, int phoneId, MmTelFeatureConnectionFactory factory,
|
|
SubscriptionManagerProxy subManagerProxy, SettingsProxy settingsProxy,
|
|
BinderCacheManager binderCacheManager) {
|
|
mContext = context;
|
|
mPhoneId = phoneId;
|
|
mMmTelFeatureConnectionFactory = factory;
|
|
mTelephonyManager = context.getSystemService(TelephonyManager.class);
|
|
mSubscriptionManagerProxy = subManagerProxy;
|
|
mSettingsProxy = settingsProxy;
|
|
mConfigManager = (CarrierConfigManager) context.getSystemService(
|
|
Context.CARRIER_CONFIG_SERVICE);
|
|
// Do not multithread tests
|
|
mExecutor = Runnable::run;
|
|
mBinderCache = binderCacheManager;
|
|
// MmTelFeatureConnection should be replaced for tests with mMmTelFeatureConnectionFactory.
|
|
associate(null /*container*/, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
|
}
|
|
|
|
/*
|
|
* Returns a flag indicating whether the IMS service is available.
|
|
*/
|
|
public boolean isServiceAvailable() {
|
|
return mMmTelConnectionRef.get().isBinderAlive();
|
|
}
|
|
|
|
/*
|
|
* Returns a flag indicating whether the IMS service is ready to send requests to lower layers.
|
|
*/
|
|
public boolean isServiceReady() {
|
|
return mMmTelConnectionRef.get().isBinderReady();
|
|
}
|
|
|
|
/**
|
|
* Opens the IMS service for making calls and/or receiving generic IMS calls as well as
|
|
* register listeners for ECBM, Multiendpoint, and UT if the ImsService supports it.
|
|
* <p>
|
|
* 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}
|
|
* @param listener A {@link MmTelFeature.Listener}, which is the interface the
|
|
* {@link MmTelFeature} uses to notify the framework of updates.
|
|
* @param ecbmListener Listener used for ECBM indications.
|
|
* @param multiEndpointListener Listener used for multiEndpoint indications.
|
|
* @throws NullPointerException if {@code listener} is null
|
|
* @throws ImsException if calling the IMS service results in an error
|
|
*/
|
|
public void open(MmTelFeature.Listener listener, ImsEcbmStateListener ecbmListener,
|
|
ImsExternalCallStateListener multiEndpointListener) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
if (listener == null) {
|
|
throw new NullPointerException("listener can't be null");
|
|
}
|
|
|
|
try {
|
|
c.openConnection(listener, ecbmListener, multiEndpointListener);
|
|
} 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(RegistrationManager.RegistrationCallback,
|
|
* Executor)} instead.
|
|
*/
|
|
public void addRegistrationListener(ImsConnectionStateListener listener) throws ImsException {
|
|
if (listener == null) {
|
|
throw new NullPointerException("listener can't be null");
|
|
}
|
|
addRegistrationCallback(listener, getImsThreadExecutor());
|
|
// connect the ImsConnectionStateListener to the new CapabilityCallback.
|
|
addCapabilitiesCallback(new ImsMmTelManager.CapabilityCallback() {
|
|
@Override
|
|
public void onCapabilitiesStatusChanged(
|
|
MmTelFeature.MmTelCapabilities capabilities) {
|
|
listener.onFeatureCapabilityChangedAdapter(getRegistrationTech(), capabilities);
|
|
}
|
|
}, getImsThreadExecutor());
|
|
log("Registration Callback registered.");
|
|
}
|
|
|
|
/**
|
|
* Adds a callback that gets called when IMS registration has changed for the slot ID
|
|
* associated with this ImsManager.
|
|
* @param callback A {@link RegistrationManager.RegistrationCallback} that will notify the
|
|
* caller when IMS registration status has changed.
|
|
* @param executor The Executor that the callback should be called on.
|
|
* @throws ImsException when the ImsService connection is not available.
|
|
*/
|
|
public void addRegistrationCallback(RegistrationManager.RegistrationCallback callback,
|
|
Executor executor)
|
|
throws ImsException {
|
|
if (callback == null) {
|
|
throw new NullPointerException("registration callback can't be null");
|
|
}
|
|
|
|
try {
|
|
callback.setExecutor(executor);
|
|
mMmTelConnectionRef.get().addRegistrationCallback(callback.getBinder());
|
|
log("Registration Callback registered.");
|
|
// Only record if there isn't a RemoteException.
|
|
} catch (IllegalStateException e) {
|
|
throw new ImsException("addRegistrationCallback(IRIB)", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a previously added registration callback that was added via
|
|
* {@link #addRegistrationCallback(RegistrationManager.RegistrationCallback, Executor)} .
|
|
* @param callback A {@link RegistrationManager.RegistrationCallback} that was previously added.
|
|
*/
|
|
public void removeRegistrationListener(RegistrationManager.RegistrationCallback callback) {
|
|
if (callback == null) {
|
|
throw new NullPointerException("registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().removeRegistrationCallback(callback.getBinder());
|
|
log("Registration callback removed.");
|
|
}
|
|
|
|
/**
|
|
* Adds a callback that gets called when IMS registration has changed for a specific
|
|
* subscription.
|
|
*
|
|
* @param callback A {@link RegistrationManager.RegistrationCallback} that will notify the
|
|
* caller when IMS registration status has changed.
|
|
* @param subId The subscription ID to register this registration callback for.
|
|
* @throws RemoteException when the ImsService connection is not available.
|
|
*/
|
|
public void addRegistrationCallbackForSubscription(IImsRegistrationCallback callback, int subId)
|
|
throws RemoteException {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().addRegistrationCallbackForSubscription(callback, subId);
|
|
log("Registration Callback registered.");
|
|
// Only record if there isn't a RemoteException.
|
|
}
|
|
|
|
/**
|
|
* Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
|
|
* that is associated with a specific subscription.
|
|
*/
|
|
public void removeRegistrationCallbackForSubscription(IImsRegistrationCallback callback,
|
|
int subId) {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().removeRegistrationCallbackForSubscription(callback, subId);
|
|
}
|
|
|
|
/**
|
|
* Adds a callback that gets called when IMS emergency registration has changed for a specific
|
|
* subscription.
|
|
*
|
|
* @param callback A {@link RegistrationManager.RegistrationCallback} that will notify the
|
|
* caller when IMS registration status has changed.
|
|
* @param subId The subscription ID to register this registration callback for.
|
|
* @throws RemoteException when the ImsService connection is not available.
|
|
*/
|
|
public void addEmergencyRegistrationCallbackForSubscription(
|
|
IImsRegistrationCallback callback, int subId) throws RemoteException {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("emergency registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().addEmergencyRegistrationCallbackForSubscription(callback, subId);
|
|
log("Emergency registration Callback registered.");
|
|
// Only record if there isn't a RemoteException.
|
|
}
|
|
|
|
/**
|
|
* Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
|
|
* that is associated with a specific subscription.
|
|
*/
|
|
public void removeEmergencyRegistrationCallbackForSubscription(
|
|
IImsRegistrationCallback callback, int subId) {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("emergency registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().removeEmergencyRegistrationCallbackForSubscription(callback,
|
|
subId);
|
|
}
|
|
|
|
/**
|
|
* 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 ImsMmTelManager.CapabilityCallback} that will notify the caller when
|
|
* MMTel capability status has changed.
|
|
* @param executor The Executor that the callback should be called on.
|
|
* @throws ImsException when the ImsService connection is not available.
|
|
*/
|
|
public void addCapabilitiesCallback(ImsMmTelManager.CapabilityCallback callback,
|
|
Executor executor) throws ImsException {
|
|
if (callback == null) {
|
|
throw new NullPointerException("capabilities callback can't be null");
|
|
}
|
|
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
callback.setExecutor(executor);
|
|
c.addCapabilityCallback(callback.getBinder());
|
|
log("Capability Callback registered.");
|
|
// Only record if there isn't a RemoteException.
|
|
} catch (IllegalStateException e) {
|
|
throw new ImsException("addCapabilitiesCallback(IF)", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a previously registered {@link ImsMmTelManager.CapabilityCallback} callback.
|
|
*/
|
|
public void removeCapabilitiesCallback(ImsMmTelManager.CapabilityCallback callback) {
|
|
if (callback == null) {
|
|
throw new NullPointerException("capabilities callback can't be null");
|
|
}
|
|
|
|
try {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
c.removeCapabilityCallback(callback.getBinder());
|
|
} catch (ImsException e) {
|
|
log("Exception removing Capability , exception=" + e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a callback that gets called when IMS capabilities have changed for a specified
|
|
* subscription.
|
|
* @param callback A {@link ImsMmTelManager.CapabilityCallback} that will notify the caller
|
|
* when the IMS Capabilities have changed.
|
|
* @param subId The subscription that is associated with the callback.
|
|
* @throws RemoteException when the ImsService connection is not available.
|
|
*/
|
|
public void addCapabilitiesCallbackForSubscription(IImsCapabilityCallback callback, int subId)
|
|
throws RemoteException {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("registration callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().addCapabilityCallbackForSubscription(callback, subId);
|
|
log("Capability Callback registered for subscription.");
|
|
}
|
|
|
|
/**
|
|
* Removes a previously registered {@link ImsMmTelManager.CapabilityCallback} that was
|
|
* associated with a specific subscription.
|
|
*/
|
|
public void removeCapabilitiesCallbackForSubscription(IImsCapabilityCallback callback,
|
|
int subId) {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("capabilities callback can't be null");
|
|
}
|
|
mMmTelConnectionRef.get().removeCapabilityCallbackForSubscription(callback, subId);
|
|
}
|
|
|
|
/**
|
|
* 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");
|
|
}
|
|
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
c.removeRegistrationCallback(listener.getBinder());
|
|
log("Registration Callback/Listener registered.");
|
|
// Only record if there isn't a RemoteException.
|
|
}
|
|
|
|
/**
|
|
* Adds a callback that gets called when Provisioning has changed for a specified subscription.
|
|
* @param callback A {@link ProvisioningManager.Callback} that will notify the caller when
|
|
* provisioning has changed.
|
|
* @param subId The subscription that is associated with the callback.
|
|
* @throws IllegalStateException when the {@link ImsService} connection is not available.
|
|
* @throws IllegalArgumentException when the {@link IImsConfigCallback} argument is null.
|
|
*/
|
|
public void addProvisioningCallbackForSubscription(IImsConfigCallback callback, int subId) {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("provisioning callback can't be null");
|
|
}
|
|
|
|
mMmTelConnectionRef.get().addProvisioningCallbackForSubscription(callback, subId);
|
|
log("Capability Callback registered for subscription.");
|
|
}
|
|
|
|
/**
|
|
* Removes a previously registered {@link ProvisioningManager.Callback} that was associated with
|
|
* a specific subscription.
|
|
* @throws IllegalStateException when the {@link ImsService} connection is not available.
|
|
* @throws IllegalArgumentException when the {@link IImsConfigCallback} argument is null.
|
|
*/
|
|
public void removeProvisioningCallbackForSubscription(IImsConfigCallback callback, int subId) {
|
|
if (callback == null) {
|
|
throw new IllegalArgumentException("provisioning callback can't be null");
|
|
}
|
|
|
|
mMmTelConnectionRef.get().removeProvisioningCallbackForSubscription(callback, subId);
|
|
}
|
|
|
|
public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech() {
|
|
try {
|
|
return mMmTelConnectionRef.get().getRegistrationTech();
|
|
} catch (RemoteException e) {
|
|
logw("getRegistrationTech: no connection to ImsService.");
|
|
return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
|
|
}
|
|
}
|
|
|
|
public void getRegistrationTech(Consumer<Integer> callback) {
|
|
getImsThreadExecutor().execute(() -> {
|
|
try {
|
|
int tech = mMmTelConnectionRef.get().getRegistrationTech();
|
|
callback.accept(tech);
|
|
} catch (RemoteException e) {
|
|
logw("getRegistrationTech(C): no connection to ImsService.");
|
|
callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Closes the connection opened in {@link #open} and removes the associated listeners.
|
|
*/
|
|
public void close() {
|
|
mMmTelConnectionRef.get().closeConnection();
|
|
}
|
|
|
|
/**
|
|
* Create or get the existing configuration interface to provision / withdraw the supplementary
|
|
* service settings.
|
|
* <p>
|
|
* There can only be one connection to the UT interface, so this may only be called by one
|
|
* ImsManager instance. Otherwise, an IllegalStateException will be thrown.
|
|
*
|
|
* @return the Ut interface instance
|
|
* @throws ImsException if getting the Ut interface results in an error
|
|
*/
|
|
public ImsUtInterface createOrGetSupplementaryServiceConfiguration() throws ImsException {
|
|
ImsUt iUt;
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
iUt = c.createOrGetUtInterface();
|
|
if (iUt == null) {
|
|
throw new ImsException("getSupplementaryServiceConfiguration()",
|
|
ImsReasonInfo.CODE_UT_NOT_SUPPORTED);
|
|
}
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("getSupplementaryServiceConfiguration()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
return iUt;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
try {
|
|
return c.createCallProfile(serviceType, callType);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("createCallProfile()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Informs the {@link ImsService} of the {@link RtpHeaderExtensionType}s which the framework
|
|
* intends to use for incoming and outgoing calls.
|
|
* <p>
|
|
* See {@link RtpHeaderExtensionType} for more information.
|
|
* @param types The RTP header extension types to use for incoming and outgoing calls, or
|
|
* empty list if none defined.
|
|
* @throws ImsException
|
|
*/
|
|
public void setOfferedRtpHeaderExtensionTypes(@NonNull Set<RtpHeaderExtensionType> types)
|
|
throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
try {
|
|
c.changeOfferedRtpHeaderExtensionTypes(types);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("setOfferedRtpHeaderExtensionTypes()", 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);
|
|
}
|
|
|
|
// Check we are still alive
|
|
getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
ImsCall call = new ImsCall(mContext, profile);
|
|
|
|
call.setListener(listener);
|
|
ImsCallSession session = createCallSession(profile);
|
|
|
|
if ((callees != null) && (callees.length == 1) && !(session.isMultiparty())) {
|
|
call.start(session, callees[0]);
|
|
} else {
|
|
call.start(session, callees);
|
|
}
|
|
|
|
return call;
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link ImsCall} to take an incoming call.
|
|
*
|
|
* @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, ImsCall.Listener listener)
|
|
throws ImsException {
|
|
// Check we are still alive
|
|
getOrThrowExceptionIfServiceUnavailable();
|
|
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);
|
|
|
|
if (Flags.ignoreAlreadyTerminatedIncomingCallBeforeRegisteringListener()){
|
|
// If the call session already terminated before registering callback then the
|
|
// framework should ignore incoming call.
|
|
if (!ImsCall.isSessionAlive(call.getSession())) {
|
|
loge("takeCall : ImsCallSession is not alive");
|
|
throw new ImsException("takeCall() : ImsCallSession is not alive",
|
|
ImsReasonInfo.CODE_UNSPECIFIED);
|
|
}
|
|
}
|
|
return call;
|
|
} catch (Throwable t) {
|
|
loge("takeCall caught: ", 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.
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public ImsConfig getConfigInterface() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
IImsConfig config = c.getConfig();
|
|
if (config == null) {
|
|
throw new ImsException("getConfigInterface()",
|
|
ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
|
|
}
|
|
return new ImsConfig(config);
|
|
}
|
|
|
|
/**
|
|
* Enable or disable a capability for multiple radio technologies.
|
|
*/
|
|
public void changeMmTelCapability(boolean isEnabled, int capability,
|
|
int... radioTechs) throws ImsException {
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
if (isEnabled) {
|
|
for (int tech : radioTechs) {
|
|
request.addCapabilitiesToEnableForTech(capability, tech);
|
|
}
|
|
} else {
|
|
for (int tech : radioTechs) {
|
|
request.addCapabilitiesToDisableForTech(capability, tech);
|
|
}
|
|
}
|
|
changeMmTelCapability(request);
|
|
}
|
|
|
|
private void changeMmTelCapability(CapabilityChangeRequest r) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
logi("changeMmTelCapability: changing capabilities for sub: " + getSubId()
|
|
+ ", request: " + r);
|
|
c.changeEnabledCapabilities(r, null);
|
|
ImsStatsCallback cb = getStatsCallback(mPhoneId);
|
|
if (cb == null) {
|
|
return;
|
|
}
|
|
for (CapabilityChangeRequest.CapabilityPair enabledCaps : r.getCapabilitiesToEnable()) {
|
|
cb.onEnabledMmTelCapabilitiesChanged(enabledCaps.getCapability(),
|
|
enabledCaps.getRadioTech(), true);
|
|
}
|
|
for (CapabilityChangeRequest.CapabilityPair disabledCaps :
|
|
r.getCapabilitiesToDisable()) {
|
|
cb.onEnabledMmTelCapabilitiesChanged(disabledCaps.getCapability(),
|
|
disabledCaps.getRadioTech(), false);
|
|
}
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("changeMmTelCapability(CCR)", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
private boolean updateRttConfigValue() {
|
|
// If there's no active sub anywhere on the device, enable RTT on the modem so that
|
|
// the device can make an emergency call.
|
|
|
|
boolean isActiveSubscriptionPresent = isActiveSubscriptionPresent();
|
|
boolean isCarrierSupported =
|
|
getBooleanCarrierConfig(CarrierConfigManager.KEY_RTT_SUPPORTED_BOOL)
|
|
|| !isActiveSubscriptionPresent;
|
|
|
|
int defaultRttMode =
|
|
getIntCarrierConfig(CarrierConfigManager.KEY_DEFAULT_RTT_MODE_INT);
|
|
int rttMode = mSettingsProxy.getSecureIntSetting(mContext.getContentResolver(),
|
|
Settings.Secure.RTT_CALLING_MODE, defaultRttMode);
|
|
logi("defaultRttMode = " + defaultRttMode + " rttMode = " + rttMode);
|
|
boolean isRttAlwaysOnCarrierConfig = getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_IGNORE_RTT_MODE_SETTING_BOOL);
|
|
if (isRttAlwaysOnCarrierConfig && rttMode == defaultRttMode) {
|
|
mSettingsProxy.putSecureIntSetting(mContext.getContentResolver(),
|
|
Settings.Secure.RTT_CALLING_MODE, defaultRttMode);
|
|
}
|
|
|
|
boolean isRttUiSettingEnabled = mSettingsProxy.getSecureIntSetting(
|
|
mContext.getContentResolver(), Settings.Secure.RTT_CALLING_MODE, 0) != 0;
|
|
|
|
boolean shouldImsRttBeOn = isRttUiSettingEnabled || isRttAlwaysOnCarrierConfig;
|
|
logi("update RTT: settings value: " + isRttUiSettingEnabled + " always-on carrierconfig: "
|
|
+ isRttAlwaysOnCarrierConfig
|
|
+ "isActiveSubscriptionPresent: " + isActiveSubscriptionPresent);
|
|
|
|
if (isCarrierSupported) {
|
|
setRttConfig(shouldImsRttBeOn);
|
|
} else {
|
|
setRttConfig(false);
|
|
}
|
|
return isCarrierSupported && shouldImsRttBeOn;
|
|
}
|
|
|
|
private void setRttConfig(boolean enabled) {
|
|
final int value = enabled ? ProvisioningManager.PROVISIONING_VALUE_ENABLED :
|
|
ProvisioningManager.PROVISIONING_VALUE_DISABLED;
|
|
getImsThreadExecutor().execute(() -> {
|
|
try {
|
|
logi("Setting RTT enabled to " + enabled);
|
|
getConfigInterface().setProvisionedValue(
|
|
ImsConfig.ConfigConstants.RTT_SETTING_ENABLED, value);
|
|
} catch (ImsException e) {
|
|
loge("Unable to set RTT value enabled to " + enabled + ": " + e);
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean queryMmTelCapability(
|
|
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
|
|
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
BlockingQueue<Boolean> result = new LinkedBlockingDeque<>(1);
|
|
|
|
try {
|
|
c.queryEnabledCapabilities(capability, radioTech, new IImsCapabilityCallback.Stub() {
|
|
@Override
|
|
public void onQueryCapabilityConfiguration(int resCap, int resTech,
|
|
boolean enabled) {
|
|
if (resCap == capability && resTech == radioTech) {
|
|
result.offer(enabled);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onChangeCapabilityConfigurationError(int capability,
|
|
int radioTech, int reason) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onCapabilitiesStatusChanged(int config) {
|
|
|
|
}
|
|
});
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("queryMmTelCapability()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
|
|
try {
|
|
return result.poll(RESPONSE_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
|
|
} catch (InterruptedException e) {
|
|
logw("queryMmTelCapability: interrupted while waiting for response");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean queryMmTelCapabilityStatus(
|
|
@MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
|
|
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
if (getRegistrationTech() != radioTech)
|
|
return false;
|
|
|
|
try {
|
|
|
|
MmTelFeature.MmTelCapabilities capabilities =
|
|
c.queryCapabilityStatus();
|
|
|
|
return capabilities.isCapable(capability);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("queryMmTelCapabilityStatus()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable the RTT configuration on this device.
|
|
*/
|
|
public void setRttEnabled(boolean enabled) {
|
|
if (enabled) {
|
|
// Override this setting if RTT is enabled.
|
|
setEnhanced4gLteModeSetting(true /*enabled*/);
|
|
}
|
|
setRttConfig(enabled);
|
|
}
|
|
|
|
/**
|
|
* Set the TTY mode. This is the actual tty mode (varies depending on peripheral status)
|
|
*/
|
|
public void setTtyMode(int ttyMode) throws ImsException {
|
|
boolean isNonTtyOrTtyOnVolteEnabled = isTtyOnVoLteCapable() ||
|
|
(ttyMode == TelecomManager.TTY_MODE_OFF);
|
|
|
|
boolean isNonTtyOrTtyOnWifiEnabled = isTtyOnVoWifiCapable() ||
|
|
(ttyMode == TelecomManager.TTY_MODE_OFF);
|
|
|
|
CapabilityChangeRequest request = new CapabilityChangeRequest();
|
|
updateVoiceCellFeatureValue(request, isNonTtyOrTtyOnVolteEnabled);
|
|
updateVideoCallFeatureValue(request, isNonTtyOrTtyOnVolteEnabled);
|
|
updateVoiceWifiFeatureAndProvisionedValues(request, isNonTtyOrTtyOnWifiEnabled);
|
|
// update MMTEL caps for the new configuration.
|
|
changeMmTelCapability(request);
|
|
if (isImsNeeded(request)) {
|
|
// Only turn on IMS if voice/video is enabled now in the new configuration.
|
|
turnOnIms();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the UI TTY mode. This is the preferred TTY mode that the user sets in the call
|
|
* settings screen.
|
|
* @param uiTtyMode TTY Mode, valid options are:
|
|
* - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
|
|
* - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
|
|
* - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
|
|
* - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
|
|
* @param onComplete A Message that will be called by the ImsService when it has completed this
|
|
* operation or null if not waiting for an async response. The Message must contain a
|
|
* valid {@link Message#replyTo} {@link android.os.Messenger}, since it will be passed
|
|
* through Binder to another process.
|
|
*/
|
|
public void setUiTTYMode(Context context, int uiTtyMode, Message onComplete)
|
|
throws ImsException {
|
|
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.setUiTTYMode(uiTtyMode, onComplete);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("setTTYMode()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies the change of user setting.
|
|
*
|
|
* @param enabled indicates whether the user setting for call waiting is enabled or not.
|
|
*/
|
|
public void setTerminalBasedCallWaitingStatus(boolean enabled) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.setTerminalBasedCallWaitingStatus(enabled);
|
|
} catch (ServiceSpecificException se) {
|
|
if (se.errorCode
|
|
== android.telephony.ims.ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
|
|
throw new ImsException("setTerminalBasedCallWaitingStatus()", se,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE);
|
|
} else {
|
|
throw new ImsException("setTerminalBasedCallWaitingStatus()", se,
|
|
ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR);
|
|
}
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("setTerminalBasedCallWaitingStatus()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether all of the capabilities specified are capable or not.
|
|
*/
|
|
public boolean isCapable(@ImsService.ImsServiceCapability long capabilities)
|
|
throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
return c.isCapable(capabilities);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("isCapable()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies SRVCC started.
|
|
* @param cb The callback to receive the list of {@link SrvccCall}.
|
|
*/
|
|
public void notifySrvccStarted(ISrvccStartedCallback cb)
|
|
throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.notifySrvccStarted(cb);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("notifySrvccStarted", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies SRVCC is completed, IMS service will hang up all calls.
|
|
*/
|
|
public void notifySrvccCompleted() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.notifySrvccCompleted();
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("notifySrvccCompleted", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies SRVCC failed. IMS service will recover and continue calls over IMS.
|
|
*/
|
|
public void notifySrvccFailed() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.notifySrvccFailed();
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("notifySrvccFailed", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies SRVCC is canceled. IMS service will recover and continue calls over IMS.
|
|
*/
|
|
public void notifySrvccCanceled() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.notifySrvccCanceled();
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("notifySrvccCanceled", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies that radio triggered IMS deregistration.
|
|
* @param reason the reason why the deregistration is triggered.
|
|
*/
|
|
public void triggerDeregistration(@ImsRegistrationImplBase.ImsDeregistrationReason int reason)
|
|
throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.triggerDeregistration(reason);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("triggerDeregistration", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public int getImsServiceState() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
return c.getFeatureState();
|
|
}
|
|
|
|
public void setMediaThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
|
|
MediaThreshold threshold) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
c.setMediaThreshold(sessionType, threshold);
|
|
} catch (RemoteException e) {
|
|
loge("setMediaThreshold Failed.");
|
|
}
|
|
}
|
|
|
|
public MediaQualityStatus queryMediaQualityStatus (
|
|
@MediaQualityStatus.MediaSessionType int sessionType) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
try {
|
|
return c.queryMediaQualityStatus(sessionType);
|
|
} catch (RemoteException e) {
|
|
loge("queryMediaQualityStatus Failed.");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void updateFeatureState(int state) {
|
|
mMmTelConnectionRef.get().updateFeatureState(state);
|
|
}
|
|
|
|
@Override
|
|
public void updateFeatureCapabilities(long capabilities) {
|
|
mMmTelConnectionRef.get().updateFeatureCapabilities(capabilities);
|
|
}
|
|
|
|
public void getImsServiceState(Consumer<Integer> result) {
|
|
getImsThreadExecutor().execute(() -> {
|
|
try {
|
|
result.accept(getImsServiceState());
|
|
} catch (ImsException e) {
|
|
// In the case that the ImsService is not available, report unavailable.
|
|
result.accept(ImsFeature.STATE_UNAVAILABLE);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return An Executor that should be used to execute potentially long-running operations.
|
|
*/
|
|
private Executor getImsThreadExecutor() {
|
|
return mExecutor;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
PersistableBundle b = null;
|
|
if (mConfigManager != null) {
|
|
// If an invalid subId is used, this bundle will contain default values.
|
|
b = mConfigManager.getConfigForSubId(getSubId());
|
|
}
|
|
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) {
|
|
PersistableBundle b = null;
|
|
if (mConfigManager != null) {
|
|
// If an invalid subId is used, this bundle will contain default values.
|
|
b = mConfigManager.getConfigForSubId(getSubId());
|
|
}
|
|
if (b != null) {
|
|
return b.getInt(key);
|
|
} else {
|
|
// Return static default defined in CarrierConfigManager.
|
|
return CarrierConfigManager.getDefaultConfig().getInt(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the int[] config from carrier config manager.
|
|
*
|
|
* @param key config key defined in CarrierConfigManager
|
|
* @return int[] values of the corresponding key.
|
|
*/
|
|
private int[] getIntArrayCarrierConfig(String key) {
|
|
PersistableBundle b = null;
|
|
if (mConfigManager != null) {
|
|
// If an invalid subId is used, this bundle will contain default values.
|
|
b = mConfigManager.getConfigForSubId(getSubId());
|
|
}
|
|
if (b != null) {
|
|
return b.getIntArray(key);
|
|
} else {
|
|
// Return static default defined in CarrierConfigManager.
|
|
return CarrierConfigManager.getDefaultConfig().getIntArray(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the ImsService Binder is connected. If it is not, we try to create the
|
|
* connection again.
|
|
*/
|
|
private MmTelFeatureConnection getOrThrowExceptionIfServiceUnavailable()
|
|
throws ImsException {
|
|
if (!isImsSupportedOnDevice(mContext)) {
|
|
throw new ImsException("IMS not supported on device.",
|
|
ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE);
|
|
}
|
|
MmTelFeatureConnection c = mMmTelConnectionRef.get();
|
|
if (c == null || !c.isBinderAlive()) {
|
|
throw new ImsException("Service is unavailable",
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
if (getSubId() != c.getSubId()) {
|
|
logi("Trying to get MmTelFeature when it is still setting up, curr subId=" + getSubId()
|
|
+ ", target subId=" + c.getSubId());
|
|
throw new ImsException("Service is still initializing",
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
@Override
|
|
public void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb) {
|
|
try {
|
|
ITelephony telephony = mBinderCache.listenOnBinder(cb, () -> {
|
|
try {
|
|
cb.imsFeatureRemoved(
|
|
FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
|
|
} catch (RemoteException ignore) {} // This is local.
|
|
});
|
|
|
|
if (telephony != null) {
|
|
telephony.registerMmTelFeatureCallback(slotId, cb);
|
|
} else {
|
|
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
|
|
}
|
|
} catch (ServiceSpecificException e) {
|
|
try {
|
|
switch (e.errorCode) {
|
|
case android.telephony.ims.ImsException.CODE_ERROR_UNSUPPORTED_OPERATION:
|
|
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED);
|
|
break;
|
|
default: {
|
|
cb.imsFeatureRemoved(
|
|
FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
|
|
}
|
|
}
|
|
} catch (RemoteException ignore) {} // Already dead anyway if this happens.
|
|
} catch (RemoteException e) {
|
|
try {
|
|
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
|
|
} catch (RemoteException ignore) {} // Already dead if this happens.
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void unregisterFeatureCallback(IImsServiceFeatureCallback cb) {
|
|
try {
|
|
ITelephony telephony = mBinderCache.removeRunnable(cb);
|
|
if (telephony != null) {
|
|
telephony.unregisterImsFeatureCallback(cb);
|
|
}
|
|
} catch (RemoteException e) {
|
|
// This means that telephony died, so do not worry about it.
|
|
loge("unregisterImsFeatureCallback (MMTEL), RemoteException: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void associate(ImsFeatureContainer c, int subId) {
|
|
if (c == null) {
|
|
mMmTelConnectionRef.set(mMmTelFeatureConnectionFactory.create(
|
|
mContext, mPhoneId, subId, null, null, null, null));
|
|
} else {
|
|
mMmTelConnectionRef.set(mMmTelFeatureConnectionFactory.create(
|
|
mContext, mPhoneId, subId, IImsMmTelFeature.Stub.asInterface(c.imsFeature),
|
|
c.imsConfig, c.imsRegistration, c.sipTransport));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void invalidate() {
|
|
mMmTelConnectionRef.get().onRemovedOrDied();
|
|
}
|
|
|
|
private ITelephony getITelephony() {
|
|
return mBinderCache.getBinder();
|
|
}
|
|
|
|
private static ITelephony getITelephonyInterface() {
|
|
return ITelephony.Stub.asInterface(
|
|
TelephonyFrameworkInitializer
|
|
.getTelephonyServiceManager()
|
|
.getTelephonyServiceRegisterer()
|
|
.get());
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
MmTelFeatureConnection c = mMmTelConnectionRef.get();
|
|
// Throws an exception if the ImsService Feature is not ready to accept commands.
|
|
return new ImsCallSession(c.createCallSession(profile));
|
|
} catch (RemoteException e) {
|
|
logw("CreateCallSession: Error, remote exception: " + e.getMessage());
|
|
throw new ImsException("createCallSession()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
|
|
}
|
|
}
|
|
|
|
private void log(String s) {
|
|
Rlog.d(TAG + mLogTagPostfix + " [" + mPhoneId + "]", s);
|
|
}
|
|
|
|
private void logi(String s) {
|
|
Rlog.i(TAG + mLogTagPostfix + " [" + mPhoneId + "]", s);
|
|
}
|
|
|
|
private void logw(String s) {
|
|
Rlog.w(TAG + mLogTagPostfix + " [" + mPhoneId + "]", s);
|
|
}
|
|
|
|
private void loge(String s) {
|
|
Rlog.e(TAG + mLogTagPostfix + " [" + mPhoneId + "]", s);
|
|
}
|
|
|
|
private void loge(String s, Throwable t) {
|
|
Rlog.e(TAG + mLogTagPostfix + " [" + mPhoneId + "]", s, t);
|
|
}
|
|
|
|
/**
|
|
* Used for turning on IMS.if its off already
|
|
*/
|
|
private void turnOnIms() throws ImsException {
|
|
mTelephonyManager.enableIms(mPhoneId);
|
|
}
|
|
|
|
private boolean isImsTurnOffAllowed() {
|
|
return isTurnOffImsAllowedByPlatform()
|
|
&& (!isWfcEnabledByPlatform()
|
|
|| !isWfcEnabledByUser());
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
mTelephonyManager.disableIms(mPhoneId);
|
|
}
|
|
|
|
/**
|
|
* Gets the ECBM interface to request ECBM exit.
|
|
* <p>
|
|
* This should only be called after {@link #open} has been called.
|
|
*
|
|
* @return the ECBM interface instance
|
|
* @throws ImsException if getting the ECBM interface results in an error
|
|
*/
|
|
public ImsEcbm getEcbmInterface() throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
ImsEcbm iEcbm = c.getEcbmInterface();
|
|
|
|
if (iEcbm == null) {
|
|
throw new ImsException("getEcbmInterface()",
|
|
ImsReasonInfo.CODE_ECBM_NOT_SUPPORTED);
|
|
}
|
|
return iEcbm;
|
|
}
|
|
|
|
public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
|
|
byte[] pdu) throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().sendSms(token, messageRef, format, smsc, isRetry, pdu);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("sendSms()", e, ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public void onMemoryAvailable(int token) throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().onMemoryAvailable(token);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("onMemoryAvailable()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public void acknowledgeSms(int token, int messageRef, int result) throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().acknowledgeSms(token, messageRef, result);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("acknowledgeSms()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public void acknowledgeSms(int token, int messageRef, int result, byte[] pdu) throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().acknowledgeSms(token, messageRef, result, pdu);
|
|
} 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 {
|
|
mMmTelConnectionRef.get().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 mMmTelConnectionRef.get().getSmsFormat();
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("getSmsFormat()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public void setSmsListener(IImsSmsListener listener) throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().setSmsListener(listener);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("setSmsListener()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
public void onSmsReady() throws ImsException {
|
|
try {
|
|
mMmTelConnectionRef.get().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 {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
return c.shouldProcessCall(isEmergency, numbers);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("shouldProcessCall()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets ImsManager settings back to factory defaults.
|
|
*
|
|
* @deprecated Doesn't support MSIM devices. Use {@link #factoryReset()} instead.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static void factoryReset(Context context) {
|
|
DefaultSubscriptionManagerProxy p = new DefaultSubscriptionManagerProxy(context);
|
|
ImsManager mgr = ImsManager.getInstance(context, p.getDefaultVoicePhoneId());
|
|
if (mgr != null) {
|
|
mgr.factoryReset();
|
|
}
|
|
Rlog.e(TAG, "factoryReset: ImsManager null.");
|
|
}
|
|
|
|
/**
|
|
* Resets ImsManager settings back to factory defaults.
|
|
*
|
|
* @hide
|
|
*/
|
|
public void factoryReset() {
|
|
int subId = getSubId();
|
|
if (!isSubIdValid(subId)) {
|
|
loge("factoryReset: invalid sub id, can not reset siminfo db settings; subId="
|
|
+ subId);
|
|
return;
|
|
}
|
|
// Set VoLTE to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
// Set VoWiFi to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_ENABLED,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
// Set VoWiFi mode to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_MODE,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
// Set VoWiFi roaming to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
// Set VoWiFi roaming mode to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.WFC_IMS_ROAMING_MODE,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
|
|
// Set VT to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.VT_IMS_ENABLED,
|
|
Integer.toString(SUB_PROPERTY_NOT_INITIALIZED));
|
|
|
|
// Set RCS UCE to default
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(subId,
|
|
SubscriptionManager.IMS_RCS_UCE_ENABLED, Integer.toString(
|
|
SUBINFO_PROPERTY_FALSE));
|
|
// Push settings
|
|
try {
|
|
reevaluateCapabilities();
|
|
} catch (ImsException e) {
|
|
loge("factoryReset, exception: " + e);
|
|
}
|
|
}
|
|
|
|
private boolean isDataEnabled() {
|
|
if (mTelephonyManager == null) {
|
|
loge("isDataEnabled: TelephonyManager not available, returning false...");
|
|
return false;
|
|
}
|
|
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(getSubId());
|
|
return tm.isDataConnectionAllowed();
|
|
}
|
|
|
|
private boolean isVolteProvisioned() {
|
|
return getImsProvisionedBoolNoException(CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_LTE);
|
|
}
|
|
|
|
private boolean isEabProvisioned() {
|
|
return getRcsProvisionedBoolNoException(CAPABILITY_TYPE_PRESENCE_UCE,
|
|
REGISTRATION_TECH_LTE);
|
|
}
|
|
|
|
private boolean isWfcProvisioned() {
|
|
return getImsProvisionedBoolNoException(CAPABILITY_TYPE_VOICE, REGISTRATION_TECH_IWLAN);
|
|
}
|
|
|
|
private boolean isVtProvisioned() {
|
|
return getImsProvisionedBoolNoException(CAPABILITY_TYPE_VIDEO, REGISTRATION_TECH_LTE);
|
|
}
|
|
|
|
private boolean isMmTelProvisioningRequired(int capability, int tech) {
|
|
int subId = getSubId();
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
logw("isMmTelProvisioningRequired subId is invalid");
|
|
return false;
|
|
}
|
|
|
|
ITelephony iTelephony = getITelephony();
|
|
if (iTelephony == null) {
|
|
logw("isMmTelProvisioningRequired ITelephony interface is invalid");
|
|
return false;
|
|
}
|
|
|
|
boolean required = false;
|
|
try {
|
|
required = iTelephony.isProvisioningRequiredForCapability(subId, capability,
|
|
tech);
|
|
} catch (RemoteException | IllegalArgumentException e) {
|
|
logw("isMmTelProvisioningRequired : operation failed" + " capability=" + capability
|
|
+ " tech=" + tech + ". Exception:" + e.getMessage());
|
|
}
|
|
|
|
log("MmTel Provisioning required " + required + " for capability " + capability);
|
|
return required;
|
|
}
|
|
|
|
private boolean isRcsProvisioningRequired(int capability, int tech) {
|
|
int subId = getSubId();
|
|
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
|
logw("isRcsProvisioningRequired subId is invalid");
|
|
return false;
|
|
}
|
|
|
|
ITelephony iTelephony = getITelephony();
|
|
if (iTelephony == null) {
|
|
logw("isRcsProvisioningRequired ITelephony interface is invalid");
|
|
return false;
|
|
}
|
|
|
|
boolean required = false;
|
|
try {
|
|
required = iTelephony.isRcsProvisioningRequiredForCapability(subId, capability,
|
|
tech);
|
|
} catch (RemoteException | IllegalArgumentException e) {
|
|
logw("isRcsProvisioningRequired : operation failed" + " capability=" + capability
|
|
+ " tech=" + tech + ". Exception:" + e.getMessage());
|
|
}
|
|
|
|
log("Rcs Provisioning required " + required + " for capability " + capability);
|
|
return required;
|
|
}
|
|
|
|
private static String booleanToPropertyString(boolean bool) {
|
|
return bool ? "1" : "0";
|
|
}
|
|
|
|
public int getConfigInt(int key) throws ImsException {
|
|
if (isLocalImsConfigKey(key)) {
|
|
return getLocalImsConfigKeyInt(key);
|
|
} else {
|
|
return getConfigInterface().getConfigInt(key);
|
|
}
|
|
}
|
|
|
|
public String getConfigString(int key) throws ImsException {
|
|
if (isLocalImsConfigKey(key)) {
|
|
return getLocalImsConfigKeyString(key);
|
|
} else {
|
|
return getConfigInterface().getConfigString(key);
|
|
}
|
|
}
|
|
|
|
public int setConfig(int key, int value) throws ImsException, RemoteException {
|
|
if (isLocalImsConfigKey(key)) {
|
|
return setLocalImsConfigKeyInt(key, value);
|
|
} else {
|
|
return getConfigInterface().setConfig(key, value);
|
|
}
|
|
}
|
|
|
|
public int setConfig(int key, String value) throws ImsException, RemoteException {
|
|
if (isLocalImsConfigKey(key)) {
|
|
return setLocalImsConfigKeyString(key, value);
|
|
} else {
|
|
return getConfigInterface().setConfig(key, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the configuration value that supported in frameworks.
|
|
*
|
|
* @param key, as defined in com.android.ims.ProvisioningManager.
|
|
* @return the value in Integer format
|
|
*/
|
|
private int getLocalImsConfigKeyInt(int key) {
|
|
int result = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
|
|
|
|
switch (key) {
|
|
case KEY_VOIMS_OPT_IN_STATUS:
|
|
result = isVoImsOptInEnabled() ? 1 : 0;
|
|
break;
|
|
}
|
|
log("getLocalImsConfigKeInt() for key:" + key + ", result: " + result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Gets the configuration value that supported in frameworks.
|
|
*
|
|
* @param key, as defined in com.android.ims.ProvisioningManager.
|
|
* @return the value in String format
|
|
*/
|
|
private String getLocalImsConfigKeyString(int key) {
|
|
String result = "";
|
|
|
|
switch (key) {
|
|
case KEY_VOIMS_OPT_IN_STATUS:
|
|
result = booleanToPropertyString(isVoImsOptInEnabled());
|
|
|
|
break;
|
|
}
|
|
log("getLocalImsConfigKeyString() for key:" + key + ", result: " + result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets the configuration value that supported in frameworks.
|
|
*
|
|
* @param key, as defined in com.android.ims.ProvisioningManager.
|
|
* @param value in Integer format.
|
|
* @return as defined in com.android.ims.ProvisioningManager#OperationStatusConstants
|
|
*/
|
|
private int setLocalImsConfigKeyInt(int key, int value) throws ImsException, RemoteException {
|
|
int result = ImsConfig.OperationStatusConstants.UNKNOWN;
|
|
|
|
switch (key) {
|
|
case KEY_VOIMS_OPT_IN_STATUS:
|
|
result = setVoImsOptInSetting(value);
|
|
reevaluateCapabilities();
|
|
break;
|
|
}
|
|
log("setLocalImsConfigKeyInt() for" +
|
|
" key: " + key +
|
|
", value: " + value +
|
|
", result: " + result);
|
|
|
|
// Notify ims config changed
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
IImsConfig config = c.getConfig();
|
|
config.notifyIntImsConfigChanged(key, value);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sets the configuration value that supported in frameworks.
|
|
*
|
|
* @param key, as defined in com.android.ims.ProvisioningManager.
|
|
* @param value in String format.
|
|
* @return as defined in com.android.ims.ProvisioningManager#OperationStatusConstants
|
|
*/
|
|
private int setLocalImsConfigKeyString(int key, String value)
|
|
throws ImsException, RemoteException {
|
|
int result = ImsConfig.OperationStatusConstants.UNKNOWN;
|
|
|
|
switch (key) {
|
|
case KEY_VOIMS_OPT_IN_STATUS:
|
|
result = setVoImsOptInSetting(Integer.parseInt(value));
|
|
reevaluateCapabilities();
|
|
break;
|
|
}
|
|
log("setLocalImsConfigKeyString() for" +
|
|
" key: " + key +
|
|
", value: " + value +
|
|
", result: " + result);
|
|
|
|
// Notify ims config changed
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
IImsConfig config = c.getConfig();
|
|
config.notifyStringImsConfigChanged(key, value);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Check the config whether supported by framework.
|
|
*
|
|
* @param key, as defined in com.android.ims.ProvisioningManager.
|
|
* @return true if the config is supported by framework.
|
|
*/
|
|
private boolean isLocalImsConfigKey(int key) {
|
|
return Arrays.stream(LOCAL_IMS_CONFIG_KEYS).anyMatch(value -> value == key);
|
|
}
|
|
|
|
private boolean isVoImsOptInEnabled() {
|
|
int setting = mSubscriptionManagerProxy.getIntegerSubscriptionProperty(
|
|
getSubId(), SubscriptionManager.VOIMS_OPT_IN_STATUS,
|
|
SUB_PROPERTY_NOT_INITIALIZED);
|
|
return (setting == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
|
|
}
|
|
|
|
private int setVoImsOptInSetting(int value) {
|
|
mSubscriptionManagerProxy.setSubscriptionProperty(
|
|
getSubId(),
|
|
SubscriptionManager.VOIMS_OPT_IN_STATUS,
|
|
String.valueOf(value));
|
|
return ImsConfig.OperationStatusConstants.SUCCESS;
|
|
}
|
|
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
pw.println("ImsManager:");
|
|
pw.println(" device supports IMS = " + isImsSupportedOnDevice(mContext));
|
|
pw.println(" mPhoneId = " + mPhoneId);
|
|
pw.println(" mConfigUpdated = " + mConfigUpdated);
|
|
pw.println(" mImsServiceProxy = " + mMmTelConnectionRef.get());
|
|
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(" isVoImsOptInEnabled = " + isVoImsOptInEnabled());
|
|
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(non-roaming) = " + getWfcMode(false));
|
|
pw.println(" getWfcMode(roaming) = " + getWfcMode(true));
|
|
pw.println(" isWfcRoamingEnabledByUser = " + isWfcRoamingEnabledByUser());
|
|
|
|
pw.println(" isVtProvisionedOnDevice = " + isVtProvisionedOnDevice());
|
|
pw.println(" isWfcProvisionedOnDevice = " + isWfcProvisionedOnDevice());
|
|
|
|
pw.println(" isCrossSimEnabledByPlatform = " + isCrossSimEnabledByPlatform());
|
|
pw.println(" isCrossSimCallingEnabledByUser = " + isCrossSimCallingEnabledByUser());
|
|
pw.println(" isImsOverNrEnabledByPlatform = " + isImsOverNrEnabledByPlatform());
|
|
pw.flush();
|
|
}
|
|
|
|
/**
|
|
* Determines if a sub id is valid.
|
|
* Mimics the logic in SubscriptionController.validateSubId.
|
|
* @param subId The sub id to check.
|
|
* @return {@code true} if valid, {@code false} otherwise.
|
|
*/
|
|
private boolean isSubIdValid(int subId) {
|
|
return mSubscriptionManagerProxy.isValidSubscriptionId(subId) &&
|
|
subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
|
|
}
|
|
|
|
private boolean isActiveSubscriptionPresent() {
|
|
return mSubscriptionManagerProxy.getActiveSubscriptionIdList().length > 0;
|
|
}
|
|
|
|
private void updateImsCarrierConfigs(PersistableBundle configs) throws ImsException {
|
|
MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
|
|
|
|
IImsConfig config = c.getConfig();
|
|
if (config == null) {
|
|
throw new ImsException("getConfigInterface()",
|
|
ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
|
|
}
|
|
try {
|
|
config.updateImsCarrierConfigs(configs);
|
|
} catch (RemoteException e) {
|
|
throw new ImsException("updateImsCarrierConfigs()", e,
|
|
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine whether to override roaming Wi-Fi calling preference when device is connected to
|
|
* non-terrestrial network.
|
|
*
|
|
* @return {@code true} if phone is connected to non-terrestrial network and if
|
|
* {@link CarrierConfigManager#KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL} is true,
|
|
* {@code false} otherwise.
|
|
*/
|
|
private boolean overrideWfcRoamingModeWhileUsingNTN() {
|
|
if (!Flags.carrierEnabledSatelliteFlag()) {
|
|
return false;
|
|
}
|
|
|
|
if (mTelephonyManager == null) {
|
|
return false;
|
|
}
|
|
|
|
TelephonyManager tm = mTelephonyManager.createForSubscriptionId(getSubId());
|
|
ServiceState serviceState = tm.getServiceState();
|
|
if (serviceState == null) {
|
|
return false;
|
|
}
|
|
|
|
if (!serviceState.isUsingNonTerrestrialNetwork()) {
|
|
return false;
|
|
}
|
|
|
|
return getBooleanCarrierConfig(
|
|
CarrierConfigManager.KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL);
|
|
}
|
|
}
|