| /* |
| * Copyright (C) 2018 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.internal.telephony; |
| |
| import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; |
| import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT; |
| |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.AsyncResult; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.RegistrantList; |
| import android.os.SystemProperties; |
| import android.provider.DeviceConfig; |
| import android.sysprop.TelephonyProperties; |
| import android.telephony.PhoneCapability; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.telephony.TelephonyRegistryManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.flags.FeatureFlags; |
| import com.android.internal.telephony.subscription.SubscriptionManagerService; |
| import com.android.telephony.Rlog; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| |
| /** |
| * This class manages phone's configuration which defines the potential capability (static) of the |
| * phone and its current activated capability (current). |
| * It gets and monitors static and current phone capability from the modem; send broadcast |
| * if they change, and sends commands to modem to enable or disable phones. |
| */ |
| public class PhoneConfigurationManager { |
| public static final String DSDA = "dsda"; |
| public static final String DSDS = "dsds"; |
| public static final String TSTS = "tsts"; |
| public static final String SSSS = ""; |
| /** DeviceConfig key for whether Virtual DSDA is enabled. */ |
| private static final String KEY_ENABLE_VIRTUAL_DSDA = "enable_virtual_dsda"; |
| private static final String LOG_TAG = "PhoneCfgMgr"; |
| private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100; |
| private static final int EVENT_GET_MODEM_STATUS = 101; |
| private static final int EVENT_GET_MODEM_STATUS_DONE = 102; |
| private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103; |
| private static final int EVENT_DEVICE_CONFIG_CHANGED = 104; |
| private static final int EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE = 105; |
| private static final int EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED = 106; |
| |
| /** |
| * Listener interface for events related to the {@link PhoneConfigurationManager} which should |
| * be reported to the {@link SimultaneousCallingTracker}. |
| */ |
| public interface Listener { |
| public void onPhoneCapabilityChanged(); |
| public void onDeviceConfigChanged(); |
| } |
| |
| /** |
| * Base listener implementation. |
| */ |
| public abstract static class ListenerBase implements Listener { |
| @Override |
| public void onPhoneCapabilityChanged() {} |
| @Override |
| public void onDeviceConfigChanged() {} |
| } |
| |
| |
| private static PhoneConfigurationManager sInstance = null; |
| private final Context mContext; |
| // Static capability retrieved from the modem - may be null in the case where no info has been |
| // retrieved yet. |
| private PhoneCapability mStaticCapability = null; |
| private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3); |
| private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3); |
| private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners = |
| new HashSet<>(1); |
| private final RadioConfig mRadioConfig; |
| private final Handler mHandler; |
| // mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as |
| // well. That is, the array size can be 2 even if num of active modems is 1. |
| private Phone[] mPhones; |
| private final Map<Integer, Boolean> mPhoneStatusMap; |
| private MockableInterface mMi = new MockableInterface(); |
| private TelephonyManager mTelephonyManager; |
| |
| /** Feature flags */ |
| @NonNull |
| private final FeatureFlags mFeatureFlags; |
| private final DefaultPhoneNotifier mNotifier; |
| public Set<Listener> mListeners = new CopyOnWriteArraySet<>(); |
| |
| /** |
| * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical |
| * modem, is enabled. |
| */ |
| private boolean mVirtualDsdaEnabled; |
| private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList(); |
| private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; |
| private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem"; |
| private static final boolean DEBUG = !"user".equals(Build.TYPE); |
| /** |
| * Init method to instantiate the object |
| * Should only be called once. |
| */ |
| public static PhoneConfigurationManager init(Context context, |
| @NonNull FeatureFlags featureFlags) { |
| synchronized (PhoneConfigurationManager.class) { |
| if (sInstance == null) { |
| sInstance = new PhoneConfigurationManager(context, featureFlags); |
| } else { |
| Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); |
| } |
| return sInstance; |
| } |
| } |
| |
| /** |
| * Constructor. |
| * @param context context needed to send broadcast. |
| */ |
| private PhoneConfigurationManager(Context context, @NonNull FeatureFlags featureFlags) { |
| mContext = context; |
| mFeatureFlags = featureFlags; |
| // TODO: send commands to modem once interface is ready. |
| mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| mRadioConfig = RadioConfig.getInstance(); |
| mHandler = new ConfigManagerHandler(); |
| mPhoneStatusMap = new HashMap<>(); |
| mVirtualDsdaEnabled = DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false); |
| mNotifier = new DefaultPhoneNotifier(mContext, mFeatureFlags); |
| DeviceConfig.addOnPropertiesChangedListener( |
| DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run, |
| properties -> { |
| if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY, |
| properties.getNamespace())) { |
| mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED); |
| } |
| }); |
| |
| notifyCapabilityChanged(); |
| |
| mPhones = PhoneFactory.getPhones(); |
| |
| for (Phone phone : mPhones) { |
| registerForRadioState(phone); |
| } |
| } |
| |
| /** |
| * Assign a listener to be notified of state changes. |
| * |
| * @param listener A listener. |
| */ |
| public void addListener(Listener listener) { |
| mListeners.add(listener); |
| } |
| |
| /** |
| * Removes a listener. |
| * |
| * @param listener A listener. |
| */ |
| public final void removeListener(Listener listener) { |
| mListeners.remove(listener); |
| } |
| |
| /** |
| * Updates the mapping between the slot IDs that support simultaneous calling and the |
| * associated sub IDs as well as notifies listeners. |
| */ |
| private void updateSimultaneousSubIdsFromPhoneIdMappingAndNotify() { |
| if (!mFeatureFlags.simultaneousCallingIndications()) return; |
| Set<Integer> slotCandidates = mSlotsSupportingSimultaneousCellularCalls.stream() |
| .map(i -> mPhones[i].getSubId()) |
| .filter(i ->i > SubscriptionManager.INVALID_SUBSCRIPTION_ID) |
| .collect(Collectors.toSet()); |
| if (mSubIdsSupportingSimultaneousCellularCalls.equals(slotCandidates)) return; |
| log("updateSimultaneousSubIdsFromPhoneIdMapping update: " |
| + mSubIdsSupportingSimultaneousCellularCalls + " -> " + slotCandidates); |
| mSubIdsSupportingSimultaneousCellularCalls.clear(); |
| mSubIdsSupportingSimultaneousCellularCalls.addAll(slotCandidates); |
| mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged( |
| mSubIdsSupportingSimultaneousCellularCalls); |
| } |
| |
| private void registerForRadioState(Phone phone) { |
| phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone); |
| } |
| |
| private PhoneCapability getDefaultCapability() { |
| if (getPhoneCount() > 1) { |
| return PhoneCapability.DEFAULT_DSDS_CAPABILITY; |
| } else { |
| return PhoneCapability.DEFAULT_SSSS_CAPABILITY; |
| } |
| } |
| |
| /** |
| * If virtual DSDA is enabled for this UE, then increase maxActiveVoiceSubscriptions to 2. |
| */ |
| private PhoneCapability maybeOverrideMaxActiveVoiceSubscriptions( |
| final PhoneCapability staticCapability) { |
| boolean isVDsdaEnabled = staticCapability.getLogicalModemList().size() > 1 |
| && mVirtualDsdaEnabled; |
| boolean isBkwdCompatDsdaEnabled = mFeatureFlags.simultaneousCallingIndications() |
| && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA); |
| if (isVDsdaEnabled || isBkwdCompatDsdaEnabled) { |
| // Since we already initialized maxActiveVoiceSubscriptions to the count the |
| // modem is capable of, we are only able to increase that count via this method. We do |
| // not allow a decrease of maxActiveVoiceSubscriptions: |
| int updatedMaxActiveVoiceSubscriptions = |
| Math.max(staticCapability.getMaxActiveVoiceSubscriptions(), 2); |
| return new PhoneCapability.Builder(staticCapability) |
| .setMaxActiveVoiceSubscriptions(updatedMaxActiveVoiceSubscriptions) |
| .build(); |
| } else { |
| return staticCapability; |
| } |
| } |
| |
| private void maybeEnableCellularDSDASupport() { |
| boolean bkwdsCompatDsda = mFeatureFlags.simultaneousCallingIndications() |
| && getPhoneCount() > 1 |
| && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA); |
| boolean halSupportSimulCalling = mRadioConfig != null |
| && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual( |
| RIL.RADIO_HAL_VERSION_2_2) |
| && getPhoneCount() > 1 |
| && getCellularStaticPhoneCapability().getMaxActiveVoiceSubscriptions() > 1; |
| // Register for simultaneous calling support changes in the modem if the HAL supports it |
| if (halSupportSimulCalling) { |
| updateSimultaneousCallingSupport(); |
| mRadioConfig.registerForSimultaneousCallingSupportStatusChanged(mHandler, |
| EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED, null); |
| } else if (bkwdsCompatDsda) { |
| // For older devices that only declare that they support DSDA via modem config, |
| // set DSDA as capable now statically. |
| log("DSDA modem config detected - setting DSDA enabled"); |
| for (Phone p : mPhones) { |
| mSlotsSupportingSimultaneousCellularCalls.add(p.getPhoneId()); |
| } |
| updateSimultaneousSubIdsFromPhoneIdMappingAndNotify(); |
| notifySimultaneousCellularCallingSlotsChanged(); |
| } |
| // Register for subId updates to notify listeners when simultaneous calling is configured |
| if (mFeatureFlags.simultaneousCallingIndications() |
| && (bkwdsCompatDsda || halSupportSimulCalling)) { |
| mContext.getSystemService(TelephonyRegistryManager.class) |
| .addOnSubscriptionsChangedListener( |
| new SubscriptionManager.OnSubscriptionsChangedListener() { |
| @Override |
| public void onSubscriptionsChanged() { |
| updateSimultaneousSubIdsFromPhoneIdMappingAndNotify(); |
| } |
| }, mHandler::post); |
| } |
| } |
| |
| /** |
| * Static method to get instance. |
| */ |
| public static PhoneConfigurationManager getInstance() { |
| if (sInstance == null) { |
| Log.wtf(LOG_TAG, "getInstance null"); |
| } |
| |
| return sInstance; |
| } |
| |
| /** |
| * Handler class to handle callbacks |
| */ |
| private final class ConfigManagerHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| Phone phone = null; |
| switch (msg.what) { |
| case Phone.EVENT_RADIO_AVAILABLE: |
| case Phone.EVENT_RADIO_ON: |
| log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.userObj != null && ar.userObj instanceof Phone) { |
| phone = (Phone) ar.userObj; |
| updatePhoneStatus(phone); |
| } else { |
| // phone is null |
| log("Unable to add phoneStatus to cache. " |
| + "No phone object provided for event " + msg.what); |
| } |
| updateRadioCapability(); |
| break; |
| case EVENT_SWITCH_DSDS_CONFIG_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| int numOfLiveModems = msg.arg1; |
| onMultiSimConfigChanged(numOfLiveModems); |
| } else { |
| log(msg.what + " failure. Not switching multi-sim config." + ar.exception); |
| } |
| break; |
| case EVENT_GET_MODEM_STATUS_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| int phoneId = msg.arg1; |
| boolean enabled = (boolean) ar.result; |
| // update the cache each time getModemStatus is requested |
| addToPhoneStatusCache(phoneId, enabled); |
| } else { |
| log(msg.what + " failure. Not updating modem status." + ar.exception); |
| } |
| break; |
| case EVENT_GET_PHONE_CAPABILITY_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| setStaticPhoneCapability((PhoneCapability) ar.result); |
| notifyCapabilityChanged(); |
| for (Listener l : mListeners) { |
| l.onPhoneCapabilityChanged(); |
| } |
| maybeEnableCellularDSDASupport(); |
| } else { |
| log(msg.what + " failure. Not getting phone capability." + ar.exception); |
| } |
| break; |
| case EVENT_DEVICE_CONFIG_CHANGED: |
| boolean isVirtualDsdaEnabled = DeviceConfig.getBoolean( |
| DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false); |
| if (isVirtualDsdaEnabled != mVirtualDsdaEnabled) { |
| log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to " |
| + isVirtualDsdaEnabled); |
| mVirtualDsdaEnabled = isVirtualDsdaEnabled; |
| for (Listener l : mListeners) { |
| l.onDeviceConfigChanged(); |
| } |
| } |
| break; |
| case EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED: |
| case EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE: |
| log("Received EVENT_SLOTS_SUPPORTING_SIMULTANEOUS_CALL_CHANGED/DONE"); |
| if (getPhoneCount() < 2) { |
| if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) { |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| } |
| break; |
| } |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| List<Integer> returnedArrayList = (List<Integer>) ar.result; |
| if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) { |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| } |
| int maxValidPhoneSlot = getPhoneCount() - 1; |
| for (int i : returnedArrayList) { |
| if (i < 0 || i > maxValidPhoneSlot) { |
| loge("Invalid slot supporting DSDA =" + i + ". Disabling DSDA."); |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| break; |
| } |
| mSlotsSupportingSimultaneousCellularCalls.add(i); |
| } |
| // Ensure the slots supporting cellular DSDA does not exceed the phone count |
| if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) { |
| loge("Invalid size of DSDA slots. Disabling cellular DSDA."); |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| } |
| } else { |
| log(msg.what + " failure. Not getting logical slots that support " |
| + "simultaneous calling." + ar.exception); |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| } |
| if (mFeatureFlags.simultaneousCallingIndications()) { |
| updateSimultaneousSubIdsFromPhoneIdMappingAndNotify(); |
| notifySimultaneousCellularCallingSlotsChanged(); |
| } |
| break; |
| default: |
| log("Unknown event: " + msg.what); |
| } |
| } |
| } |
| |
| /** |
| * Enable or disable phone |
| * |
| * @param phone which phone to operate on |
| * @param enable true or false |
| * @param result the message to sent back when it's done. |
| */ |
| public void enablePhone(Phone phone, boolean enable, Message result) { |
| if (phone == null) { |
| log("enablePhone failed phone is null"); |
| return; |
| } |
| phone.mCi.enableModem(enable, result); |
| } |
| |
| /** |
| * Get phone status (enabled/disabled) |
| * first query cache, if the status is not in cache, |
| * add it to cache and return a default value true (non-blocking). |
| * |
| * @param phone which phone to operate on |
| */ |
| public boolean getPhoneStatus(Phone phone) { |
| if (phone == null) { |
| log("getPhoneStatus failed phone is null"); |
| return false; |
| } |
| |
| int phoneId = phone.getPhoneId(); |
| |
| //use cache if the status has already been updated/queried |
| try { |
| return getPhoneStatusFromCache(phoneId); |
| } catch (NoSuchElementException ex) { |
| // Return true if modem status cannot be retrieved. For most cases, modem status |
| // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not |
| // supported. Modem is always on. |
| //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629 |
| return true; |
| } finally { |
| //in either case send an asynchronous request to retrieve the phone status |
| updatePhoneStatus(phone); |
| } |
| } |
| |
| /** |
| * Get phone status (enabled/disabled) directly from modem, and use a result Message object |
| * Note: the caller of this method is reponsible to call this in a blocking fashion as well |
| * as read the results and handle the error case. |
| * (In order to be consistent, in error case, we should return default value of true; refer |
| * to #getPhoneStatus method) |
| * |
| * @param phone which phone to operate on |
| * @param result message that will be updated with result |
| */ |
| public void getPhoneStatusFromModem(Phone phone, Message result) { |
| if (phone == null) { |
| log("getPhoneStatus failed phone is null"); |
| } |
| phone.mCi.getModemStatus(result); |
| } |
| |
| /** |
| * return modem status from cache, NoSuchElementException if phoneId not in cache |
| * @param phoneId |
| */ |
| public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException { |
| if (mPhoneStatusMap.containsKey(phoneId)) { |
| return mPhoneStatusMap.get(phoneId); |
| } else { |
| throw new NoSuchElementException("phoneId not found: " + phoneId); |
| } |
| } |
| |
| /** |
| * method to call RIL getModemStatus |
| */ |
| private void updatePhoneStatus(Phone phone) { |
| Message result = Message.obtain( |
| mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/); |
| phone.mCi.getModemStatus(result); |
| } |
| |
| /** |
| * Add status of the phone to the status HashMap |
| * @param phoneId |
| * @param status |
| */ |
| public void addToPhoneStatusCache(int phoneId, boolean status) { |
| mPhoneStatusMap.put(phoneId, status); |
| } |
| |
| /** |
| * Returns how many phone objects the device supports. |
| */ |
| public int getPhoneCount() { |
| return mTelephonyManager.getActiveModemCount(); |
| } |
| |
| /** |
| * @return The updated list of logical slots that support simultaneous cellular calling from the |
| * modem based on current network conditions. |
| */ |
| public Set<Integer> getSlotsSupportingSimultaneousCellularCalls() { |
| return mSlotsSupportingSimultaneousCellularCalls; |
| } |
| |
| /** |
| * Get the current the list of logical slots supporting simultaneous cellular calling from the |
| * modem based on current network conditions. |
| */ |
| @VisibleForTesting |
| public void updateSimultaneousCallingSupport() { |
| log("updateSimultaneousCallingSupport: sending the request for " |
| + "getting the list of logical slots supporting simultaneous cellular calling"); |
| Message callback = Message.obtain( |
| mHandler, EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE); |
| mRadioConfig.updateSimultaneousCallingSupport(callback); |
| log("updateSimultaneousCallingSupport: " |
| + "mSlotsSupportingSimultaneousCellularCalls = " + |
| mSlotsSupportingSimultaneousCellularCalls); |
| } |
| |
| /** |
| * @return static overall phone capabilities for all phones, including voice overrides. |
| */ |
| public synchronized PhoneCapability getStaticPhoneCapability() { |
| boolean isDefault = mStaticCapability == null; |
| PhoneCapability caps = isDefault ? getDefaultCapability() : mStaticCapability; |
| caps = maybeOverrideMaxActiveVoiceSubscriptions(caps); |
| log("getStaticPhoneCapability: isDefault=" + isDefault + ", caps=" + caps); |
| return caps; |
| } |
| |
| /** |
| * @return untouched capabilities returned from the modem |
| */ |
| private synchronized PhoneCapability getCellularStaticPhoneCapability() { |
| log("getCellularStaticPhoneCapability: mStaticCapability " + mStaticCapability); |
| return mStaticCapability; |
| } |
| |
| /** |
| * Caches the static PhoneCapability returned by the modem |
| */ |
| public synchronized void setStaticPhoneCapability(PhoneCapability capability) { |
| log("setStaticPhoneCapability: mStaticCapability " + capability); |
| mStaticCapability = capability; |
| } |
| |
| /** |
| * Query the modem to return its static PhoneCapability and cache it |
| */ |
| @VisibleForTesting |
| public void updateRadioCapability() { |
| log("updateRadioCapability: sending the request for getting PhoneCapability"); |
| Message callback = Message.obtain(mHandler, EVENT_GET_PHONE_CAPABILITY_DONE); |
| mRadioConfig.getPhoneCapability(callback); |
| } |
| |
| /** |
| * get configuration related status of each phone. |
| */ |
| public PhoneCapability getCurrentPhoneCapability() { |
| return getStaticPhoneCapability(); |
| } |
| |
| public int getNumberOfModemsWithSimultaneousDataConnections() { |
| return getStaticPhoneCapability().getMaxActiveDataSubscriptions(); |
| } |
| |
| public int getNumberOfModemsWithSimultaneousVoiceConnections() { |
| return getStaticPhoneCapability().getMaxActiveVoiceSubscriptions(); |
| } |
| |
| public boolean isVirtualDsdaEnabled() { |
| return mVirtualDsdaEnabled; |
| } |
| |
| /** |
| * Register to listen to changes in the Phone slots that support simultaneous calling. |
| * @param consumer A consumer that will be used to consume the new slots supporting simultaneous |
| * cellular calling when it changes. |
| */ |
| public void registerForSimultaneousCellularCallingSlotsChanged( |
| Consumer<Set<Integer>> consumer) { |
| mSimultaneousCellularCallingListeners.add(consumer); |
| } |
| |
| private void notifySimultaneousCellularCallingSlotsChanged() { |
| log("notifying listeners of changes to simultaneous cellular calling - new state:" |
| + mSlotsSupportingSimultaneousCellularCalls); |
| for (Consumer<Set<Integer>> consumer : mSimultaneousCellularCallingListeners) { |
| try { |
| consumer.accept(new HashSet<>(mSlotsSupportingSimultaneousCellularCalls)); |
| } catch (Exception e) { |
| log("Unexpected Exception encountered when notifying listener: " + e); |
| } |
| } |
| } |
| |
| private void notifyCapabilityChanged() { |
| mNotifier.notifyPhoneCapabilityChanged(getStaticPhoneCapability()); |
| } |
| |
| /** |
| * Switch configs to enable multi-sim or switch back to single-sim |
| * @param numOfSims number of active sims we want to switch to |
| */ |
| public void switchMultiSimConfig(int numOfSims) { |
| log("switchMultiSimConfig: with numOfSims = " + numOfSims); |
| if (getStaticPhoneCapability().getLogicalModemList().size() < numOfSims) { |
| log("switchMultiSimConfig: Phone is not capable of enabling " |
| + numOfSims + " sims, exiting!"); |
| return; |
| } |
| if (getPhoneCount() != numOfSims) { |
| log("switchMultiSimConfig: sending the request for switching"); |
| Message callback = Message.obtain( |
| mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/); |
| mRadioConfig.setNumOfLiveModems(numOfSims, callback); |
| } else { |
| log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already " |
| + numOfSims); |
| } |
| } |
| |
| /** |
| * Get whether reboot is required or not after making changes to modem configurations. |
| * Return value defaults to true |
| */ |
| public boolean isRebootRequiredForModemConfigChange() { |
| return mMi.isRebootRequiredForModemConfigChange(); |
| } |
| |
| private void onMultiSimConfigChanged(int numOfActiveModems) { |
| int oldNumOfActiveModems = getPhoneCount(); |
| setMultiSimProperties(numOfActiveModems); |
| |
| if (isRebootRequiredForModemConfigChange()) { |
| log("onMultiSimConfigChanged: Rebooting."); |
| PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| pm.reboot("Multi-SIM config changed."); |
| } else { |
| log("onMultiSimConfigChanged: Rebooting is not required."); |
| mMi.notifyPhoneFactoryOnMultiSimConfigChanged(mContext, numOfActiveModems); |
| broadcastMultiSimConfigChange(numOfActiveModems); |
| boolean subInfoCleared = false; |
| // if numOfActiveModems is decreasing, deregister old RILs |
| // eg if we are going from 2 phones to 1 phone, we need to deregister RIL for the |
| // second phone. This loop does nothing if numOfActiveModems is increasing. |
| for (int phoneId = numOfActiveModems; phoneId < oldNumOfActiveModems; phoneId++) { |
| SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId); |
| subInfoCleared = true; |
| mPhones[phoneId].mCi.onSlotActiveStatusChange( |
| SubscriptionManager.isValidPhoneId(phoneId)); |
| } |
| if (subInfoCleared) { |
| // This triggers update of default subs. This should be done asap after |
| // setMultiSimProperties() to avoid (minimize) duration for which default sub can be |
| // invalid and can map to a non-existent phone. |
| // If forexample someone calls a TelephonyManager API on default sub after |
| // setMultiSimProperties() and before onSubscriptionsChanged() below -- they can be |
| // using an invalid sub, which can map to a non-existent phone and can cause an |
| // exception (see b/163582235). |
| MultiSimSettingController.getInstance().onPhoneRemoved(); |
| } |
| // old phone objects are not needed now; mPhones can be updated |
| mPhones = PhoneFactory.getPhones(); |
| // if numOfActiveModems is increasing, register new RILs |
| // eg if we are going from 1 phone to 2 phones, we need to register RIL for the second |
| // phone. This loop does nothing if numOfActiveModems is decreasing. |
| for (int phoneId = oldNumOfActiveModems; phoneId < numOfActiveModems; phoneId++) { |
| Phone phone = mPhones[phoneId]; |
| registerForRadioState(phone); |
| phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(phoneId)); |
| } |
| |
| if (numOfActiveModems > 1) { |
| // Check if cellular DSDA is supported. If it is, then send a request to the |
| // modem to refresh the list of SIM slots that currently support DSDA based on |
| // current network conditions |
| maybeEnableCellularDSDASupport(); |
| } else { |
| // The number of active modems is 0 or 1, disable cellular DSDA: |
| mSlotsSupportingSimultaneousCellularCalls.clear(); |
| if (mFeatureFlags.simultaneousCallingIndications()) { |
| updateSimultaneousSubIdsFromPhoneIdMappingAndNotify(); |
| notifySimultaneousCellularCallingSlotsChanged(); |
| } |
| } |
| |
| // When the user enables DSDS mode, the default VOICE and SMS subId should be switched |
| // to "No Preference". Doing so will sync the network/sim settings and telephony. |
| // (see b/198123192) |
| if (numOfActiveModems > oldNumOfActiveModems && numOfActiveModems == 2) { |
| Log.i(LOG_TAG, " onMultiSimConfigChanged: DSDS mode enabled; " |
| + "setting VOICE & SMS subId to -1 (No Preference)"); |
| |
| //Set the default VOICE subId to -1 ("No Preference") |
| SubscriptionManagerService.getInstance().setDefaultVoiceSubId( |
| SubscriptionManager.INVALID_SUBSCRIPTION_ID); |
| |
| //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042) |
| } else { |
| Log.i(LOG_TAG, |
| "onMultiSimConfigChanged: DSDS mode NOT detected. NOT setting the " |
| + "default VOICE and SMS subId to -1 (No Preference)"); |
| } |
| } |
| } |
| |
| /** |
| * Helper method to set system properties for setting multi sim configs, |
| * as well as doing the phone reboot |
| * NOTE: In order to support more than 3 sims, we need to change this method. |
| * @param numOfActiveModems number of active sims |
| */ |
| private void setMultiSimProperties(int numOfActiveModems) { |
| mMi.setMultiSimProperties(numOfActiveModems); |
| } |
| |
| @VisibleForTesting |
| public static void notifyMultiSimConfigChange(int numOfActiveModems) { |
| sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems); |
| } |
| |
| /** |
| * Register for multi-SIM configuration change, for example if the devices switched from single |
| * SIM to dual-SIM mode. |
| * |
| * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent. |
| */ |
| public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) { |
| sMultiSimConfigChangeRegistrants.addUnique(h, what, obj); |
| } |
| |
| /** |
| * Unregister for multi-SIM configuration change. |
| */ |
| public static void unregisterForMultiSimConfigChange(Handler h) { |
| sMultiSimConfigChangeRegistrants.remove(h); |
| } |
| |
| /** |
| * Unregister for all multi-SIM configuration change events. |
| */ |
| public static void unregisterAllMultiSimConfigChangeRegistrants() { |
| sMultiSimConfigChangeRegistrants.removeAll(); |
| } |
| |
| private void broadcastMultiSimConfigChange(int numOfActiveModems) { |
| log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems); |
| // Notify internal registrants first. |
| notifyMultiSimConfigChange(numOfActiveModems); |
| |
| Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); |
| intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems); |
| mContext.sendBroadcast(intent); |
| } |
| /** |
| * This is invoked from shell commands during CTS testing only. |
| * @return true if the modem service is set successfully, false otherwise. |
| */ |
| public boolean setModemService(String serviceName) { |
| log("setModemService: " + serviceName); |
| boolean statusRadioConfig = false; |
| boolean statusRil = false; |
| final boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false); |
| final boolean isAllowedForBoot = |
| SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false); |
| |
| // Check for ALLOW_MOCK_MODEM_PROPERTY and BOOT_ALLOW_MOCK_MODEM_PROPERTY on user builds |
| if (isAllowed || isAllowedForBoot || DEBUG) { |
| if (mRadioConfig != null) { |
| statusRadioConfig = mRadioConfig.setModemService(serviceName); |
| } |
| |
| if (!statusRadioConfig) { |
| loge("setModemService: switching modem service for radioconfig fail"); |
| return false; |
| } |
| |
| for (int i = 0; i < getPhoneCount(); i++) { |
| if (mPhones[i] != null) { |
| statusRil = mPhones[i].mCi.setModemService(serviceName); |
| } |
| |
| if (!statusRil) { |
| loge("setModemService: switch modem for radio " + i + " fail"); |
| |
| // Disconnect the switched service |
| mRadioConfig.setModemService(null); |
| for (int t = 0; t < i; t++) { |
| mPhones[t].mCi.setModemService(null); |
| } |
| return false; |
| } |
| } |
| } else { |
| loge("setModemService is not allowed"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * This is invoked from shell commands to query during CTS testing only. |
| * @return the service name of the connected service. |
| */ |
| public String getModemService() { |
| if (mPhones[0] == null) { |
| return ""; |
| } |
| |
| return mPhones[0].mCi.getModemService(); |
| } |
| |
| /** |
| * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests. |
| * |
| * For example, setting or reading system property are static native methods that can't be |
| * directly mocked. We can mock it by replacing MockableInterface object with a mock instance |
| * in unittest. |
| */ |
| @VisibleForTesting |
| public static class MockableInterface { |
| /** |
| * Wrapper function to decide whether reboot is required for modem config change. |
| */ |
| @VisibleForTesting |
| public boolean isRebootRequiredForModemConfigChange() { |
| boolean rebootRequired = TelephonyProperties.reboot_on_modem_change().orElse(false); |
| log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired); |
| return rebootRequired; |
| } |
| |
| /** |
| * Wrapper function to call setMultiSimProperties. |
| */ |
| @VisibleForTesting |
| public void setMultiSimProperties(int numOfActiveModems) { |
| String multiSimConfig; |
| switch(numOfActiveModems) { |
| case 3: |
| multiSimConfig = TSTS; |
| break; |
| case 2: |
| multiSimConfig = DSDS; |
| break; |
| default: |
| multiSimConfig = SSSS; |
| } |
| |
| log("setMultiSimProperties to " + multiSimConfig); |
| TelephonyProperties.multi_sim_config(multiSimConfig); |
| } |
| |
| /** |
| * Wrapper function to call PhoneFactory.onMultiSimConfigChanged. |
| */ |
| @VisibleForTesting |
| public void notifyPhoneFactoryOnMultiSimConfigChanged( |
| Context context, int numOfActiveModems) { |
| PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems); |
| } |
| |
| /** |
| * Wrapper function to query the sysprop for multi_sim_config |
| */ |
| public Optional<String> getMultiSimProperty() { |
| return TelephonyProperties.multi_sim_config(); |
| } |
| } |
| |
| private static void log(String s) { |
| Rlog.d(LOG_TAG, s); |
| } |
| |
| private static void loge(String s) { |
| Rlog.e(LOG_TAG, s); |
| } |
| |
| private static void loge(String s, Exception ex) { |
| Rlog.e(LOG_TAG, s, ex); |
| } |
| } |