diff options
author | Grant Menke <grantmenke@google.com> | 2024-02-22 18:57:47 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2024-02-22 18:57:47 +0000 |
commit | 3b0272b64aeac25f95896c06cc3c3f7181af67f7 (patch) | |
tree | a1757a5f1b0016529272db509111058c5124f1dc | |
parent | bf371a949c7ed79c4dfeeebb273f256d7beacdcf (diff) | |
download | telephony-3b0272b64aeac25f95896c06cc3c3f7181af67f7.tar.gz |
Revert^2 "Add SimultaneousCallingTracker"
bf371a949c7ed79c4dfeeebb273f256d7beacdcf
Change-Id: Ia0481d9c5d245a77b3670f3d873e931d5701b0bc
6 files changed, 1096 insertions, 10 deletions
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java index 3b47670ad1..f34ba48a94 100644 --- a/src/java/com/android/internal/telephony/Phone.java +++ b/src/java/com/android/internal/telephony/Phone.java @@ -17,6 +17,7 @@ package com.android.internal.telephony; import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO; +import static android.telephony.ims.ImsService.CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING; import android.annotation.NonNull; import android.annotation.Nullable; @@ -4620,6 +4621,20 @@ public abstract class Phone extends Handler implements PhoneInternalInterface { } } + public boolean isImsServiceSimultaneousCallingSupportCapable(Context context) { + if (!mFeatureFlags.simultaneousCallingIndications()) return false; + boolean capable = false; + ImsManager imsManager = ImsManager.getInstance(context, mPhoneId); + if (imsManager != null) { + try { + capable = imsManager.isCapable(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING); + } catch (ImsException e) { + loge("initializeTerminalBasedCallWaiting : exception " + e); + } + } + return capable; + } + public void startRingbackTone() { } diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java index 7141f379fd..49e1e387fd 100644 --- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java +++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java @@ -49,6 +49,7 @@ 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; @@ -74,6 +75,25 @@ public class PhoneConfigurationManager { 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; @@ -95,6 +115,8 @@ public class PhoneConfigurationManager { @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. @@ -156,6 +178,24 @@ public class PhoneConfigurationManager { } /** + * 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. */ @@ -305,6 +345,9 @@ public class PhoneConfigurationManager { if (ar != null && ar.exception == null) { mStaticCapability = (PhoneCapability) ar.result; notifyCapabilityChanged(); + for (Listener l : mListeners) { + l.onPhoneCapabilityChanged(); + } maybeEnableCellularDSDASupport(); } else { log(msg.what + " failure. Not getting phone capability." + ar.exception); @@ -317,6 +360,9 @@ public class PhoneConfigurationManager { log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to " + isVirtualDsdaEnabled); mVirtualDsdaEnabled = isVirtualDsdaEnabled; + for (Listener l : mListeners) { + l.onDeviceConfigChanged(); + } } break; case EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED: @@ -463,7 +509,10 @@ public class PhoneConfigurationManager { return mTelephonyManager.getActiveModemCount(); } - @VisibleForTesting + /** + * @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; } @@ -510,6 +559,14 @@ public class PhoneConfigurationManager { return mStaticCapability.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 diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java index b1ff5002e2..c5bc4283ec 100644 --- a/src/java/com/android/internal/telephony/PhoneFactory.java +++ b/src/java/com/android/internal/telephony/PhoneFactory.java @@ -98,6 +98,7 @@ public class PhoneFactory { @UnsupportedAppUsage static private Context sContext; static private PhoneConfigurationManager sPhoneConfigurationManager; + static private SimultaneousCallingTracker sSimultaneousCallingTracker; static private PhoneSwitcher sPhoneSwitcher; static private TelephonyNetworkFactory[] sTelephonyNetworkFactories; static private NotificationChannelController sNotificationChannelController; @@ -257,6 +258,10 @@ public class PhoneFactory { } sPhoneConfigurationManager = PhoneConfigurationManager.init(sContext, featureFlags); + if (featureFlags.simultaneousCallingIndications()) { + sSimultaneousCallingTracker = + SimultaneousCallingTracker.init(sContext, featureFlags); + } sCellularNetworkValidator = CellularNetworkValidator.make(sContext); diff --git a/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java new file mode 100644 index 0000000000..0a14ccd60d --- /dev/null +++ b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2024 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 android.annotation.NonNull; +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Message; +import android.telephony.AccessNetworkConstants; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyRegistryManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.flags.FeatureFlags; +import com.android.internal.telephony.imsphone.ImsPhone; +import com.android.internal.telephony.subscription.SubscriptionManagerService; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; + +public class SimultaneousCallingTracker { + private static SimultaneousCallingTracker sInstance = null; + private final Context mContext; + + /** + * A dynamic map of all voice capable {@link Phone} objects mapped to the set of {@link Phone} + * objects each {@link Phone} has a compatible user association with. To be considered + * compatible based on user association, both must be associated with the same + * {@link android.os.UserHandle} or both must be unassociated. + */ + private Map<Phone, Set<Phone>> mVoiceCapablePhoneMap = new HashMap<>(); + + @VisibleForTesting + public boolean isDeviceSimultaneousCallingCapable = false; + public Set<Listener> mListeners = new CopyOnWriteArraySet<>(); + private final PhoneConfigurationManager mPhoneConfigurationManager; + private final Handler mHandler; + + /** + * A dynamic map of all the Phone IDs mapped to the set of {@link Phone} objects each + * {@link Phone} supports simultaneous calling (DSDA) with. + */ + private Map<Integer, Set<Phone>> mSimultaneousCallPhoneSupportMap = new HashMap<>(); + private static final String LOG_TAG = "SimultaneousCallingTracker"; + protected static final int EVENT_SUBSCRIPTION_CHANGED = 101; + protected static final int EVENT_PHONE_CAPABILITY_CHANGED = 102; + protected static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 103; + protected static final int EVENT_DEVICE_CONFIG_CHANGED = 104; + protected static final int EVENT_IMS_REGISTRATION_CHANGED = 105; + + /** Feature flags */ + @NonNull + private final FeatureFlags mFeatureFlags; + + /** + * Init method to instantiate the object + * Should only be called once. + */ + public static SimultaneousCallingTracker init(Context context, + @NonNull FeatureFlags featureFlags) { + if (sInstance == null) { + sInstance = new SimultaneousCallingTracker(context, featureFlags); + } else { + Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); + } + return sInstance; + } + + /** + * Constructor. + * @param context context needed to send broadcast. + */ + private SimultaneousCallingTracker(Context context, @NonNull FeatureFlags featureFlags) { + mContext = context; + mFeatureFlags = featureFlags; + mHandler = new ConfigManagerHandler(); + mPhoneConfigurationManager = PhoneConfigurationManager.getInstance(); + mPhoneConfigurationManager.addListener(mPhoneConfigurationManagerListener); + PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler, + EVENT_MULTI_SIM_CONFIG_CHANGED, null); + TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) + context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + telephonyRegistryManager.addOnSubscriptionsChangedListener( + mSubscriptionsChangedListener, new HandlerExecutor(mHandler)); + } + + /** + * Static method to get instance. + */ + public static SimultaneousCallingTracker 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) { + if (!mFeatureFlags.simultaneousCallingIndications()) { return; } + Log.v(LOG_TAG, "Received EVENT " + msg.what); + switch (msg.what) { + case EVENT_PHONE_CAPABILITY_CHANGED -> { + checkSimultaneousCallingDeviceCapability(); + } + case EVENT_SUBSCRIPTION_CHANGED -> { + updatePhoneMapAndSimultaneousCallSupportMap(); + } + case EVENT_MULTI_SIM_CONFIG_CHANGED -> { + int activeModemCount = (int) ((AsyncResult) msg.obj).result; + if (activeModemCount > 1) { + // SSIM --> MSIM: recalculate simultaneous calling supported combinations + updatePhoneMapAndSimultaneousCallSupportMap(); + } else { + // MSIM --> SSIM: remove all simultaneous calling supported combinations + disableSimultaneousCallingSupport(); + handleSimultaneousCallingSupportChanged(); + } + } + case EVENT_DEVICE_CONFIG_CHANGED, EVENT_IMS_REGISTRATION_CHANGED -> { + updateSimultaneousCallSupportMap(); + } + default -> Log.i(LOG_TAG, "Received unknown event: " + msg.what); + } + } + } + + /** + * Listener interface for events related to the {@link SimultaneousCallingTracker}. + */ + public interface Listener { + /** + * Inform Telecom that the simultaneous calling subscription support map may have changed. + * + * @param simultaneousCallSubSupportMap Map of all voice capable subscription IDs mapped to + * a set containing the subscription IDs which that + * subscription is DSDA compatible with. + */ + public void onSimultaneousCallingSupportChanged(Map<Integer, + Set<Integer>> simultaneousCallSubSupportMap); + } + + /** + * Base listener implementation. + */ + public abstract static class ListenerBase implements SimultaneousCallingTracker.Listener { + @Override + public void onSimultaneousCallingSupportChanged(Map<Integer, + Set<Integer>> simultaneousCallSubSupportMap) {} + } + + /** + * Assign a listener to be notified of state changes. + * + * @param listener A listener. + */ + public void addListener(Listener listener) { + if (mFeatureFlags.simultaneousCallingIndications()) { + mListeners.add(listener); + } + } + + /** + * Removes a listener. + * + * @param listener A listener. + */ + public final void removeListener(Listener listener) { + if (mFeatureFlags.simultaneousCallingIndications()) { + mListeners.remove(listener); + } + } + + /** + * Listener for listening to events in the {@link android.telephony.TelephonyRegistryManager} + */ + private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener = + new SubscriptionManager.OnSubscriptionsChangedListener() { + @Override + public void onSubscriptionsChanged() { + if (!mHandler.hasMessages(EVENT_SUBSCRIPTION_CHANGED)) { + mHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED); + } + } + }; + + /** + * Listener for listening to events in the {@link PhoneConfigurationManager}. + */ + private final PhoneConfigurationManager.Listener mPhoneConfigurationManagerListener = + new PhoneConfigurationManager.Listener() { + @Override + public void onPhoneCapabilityChanged() { + if (!mHandler.hasMessages(EVENT_PHONE_CAPABILITY_CHANGED)) { + mHandler.sendEmptyMessage(EVENT_PHONE_CAPABILITY_CHANGED); + } + } + @Override + public void onDeviceConfigChanged() { + if (!mHandler.hasMessages(EVENT_DEVICE_CONFIG_CHANGED)) { + mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED); + } + } + }; + + private void checkSimultaneousCallingDeviceCapability() { + if (mPhoneConfigurationManager.getNumberOfModemsWithSimultaneousVoiceConnections() > 1) { + isDeviceSimultaneousCallingCapable = true; + mPhoneConfigurationManager.registerForSimultaneousCellularCallingSlotsChanged( + this::onSimultaneousCellularCallingSlotsChanged); + } + } + + /** + * + * @param subId to get the slots supporting simultaneous calling with + * @return the set of subId's that support simultaneous calling with the param subId + */ + public Set<Integer> getSubIdsSupportingSimultaneousCalling(int subId) { + if (!isDeviceSimultaneousCallingCapable) { + Log.v(LOG_TAG, "Device is not simultaneous calling capable"); + return Collections.emptySet(); + } + for (int phoneId : mSimultaneousCallPhoneSupportMap.keySet()) { + if (PhoneFactory.getPhone(phoneId).getSubId() == subId) { + Set<Integer> subIdsSupportingSimultaneousCalling = new HashSet<>(); + for (Phone phone : mSimultaneousCallPhoneSupportMap.get(phoneId)) { + subIdsSupportingSimultaneousCalling.add(phone.getSubId()); + } + Log.d(LOG_TAG, "getSlotsSupportingSimultaneousCalling for subId=" + subId + + "; subIdsSupportingSimultaneousCalling=[" + + getStringFromSet(subIdsSupportingSimultaneousCalling) + "]."); + return subIdsSupportingSimultaneousCalling; + } + } + Log.e(LOG_TAG, "getSlotsSupportingSimultaneousCalling: Subscription ID not found in" + + " the map of voice capable phones."); + return Collections.emptySet(); + } + + private void updatePhoneMapAndSimultaneousCallSupportMap() { + if (!isDeviceSimultaneousCallingCapable) { + Log.d(LOG_TAG, "Ignoring updatePhoneMapAndSimultaneousCallSupportMap since device " + + "is not DSDA capable."); + return; + } + unregisterForImsRegistrationChanges(mVoiceCapablePhoneMap); + mVoiceCapablePhoneMap = generateVoiceCapablePhoneMapBasedOnUserAssociation(); + Log.i(LOG_TAG, "updatePhoneMapAndSimultaneousCallSupportMap: mVoiceCapablePhoneMap.size = " + + mVoiceCapablePhoneMap.size()); + registerForImsRegistrationChanges(mVoiceCapablePhoneMap); + updateSimultaneousCallSupportMap(); + } + + private void updateSimultaneousCallSupportMap() { + if (!isDeviceSimultaneousCallingCapable) { + Log.d(LOG_TAG, "Ignoring updateSimultaneousCallSupportMap since device is not DSDA" + + "capable."); + return; + } + mSimultaneousCallPhoneSupportMap = + generateSimultaneousCallSupportMap(mVoiceCapablePhoneMap); + handleSimultaneousCallingSupportChanged(); + } + + /** + * The simultaneous cellular calling slots have changed. + * @param slotIds The Set of slotIds that have simultaneous cellular calling. + */ + private void onSimultaneousCellularCallingSlotsChanged(Set<Integer> slotIds) { + //Cellular calling slots have changed - regenerate simultaneous calling support map: + updateSimultaneousCallSupportMap(); + } + + private void disableSimultaneousCallingSupport() { + if (!isDeviceSimultaneousCallingCapable) { + Log.d(LOG_TAG, "Ignoring updateSimultaneousCallSupportMap since device is not DSDA" + + "capable."); + return; + } + unregisterForImsRegistrationChanges(mVoiceCapablePhoneMap); + + // In Single-SIM mode, simultaneous calling is not supported at all: + mSimultaneousCallPhoneSupportMap.clear(); + mVoiceCapablePhoneMap.clear(); + } + + /** + * Registers a listener to receive IMS registration changes for all phones in the phoneMap. + * + * @param phoneMap Map of voice capable phones mapped to the set of phones each has a compatible + * user association with. + */ + private void registerForImsRegistrationChanges(Map<Phone, Set<Phone>> phoneMap) { + for (Phone phone : phoneMap.keySet()) { + ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); + if (imsPhone != null) { + Log.v(LOG_TAG, "registerForImsRegistrationChanges: registering phoneId = " + + phone.getPhoneId()); + imsPhone.registerForImsRegistrationChanges(mHandler, + EVENT_IMS_REGISTRATION_CHANGED, null); + } else { + Log.v(LOG_TAG, "registerForImsRegistrationChanges: phone not recognized as " + + "ImsPhone: phoneId = " + phone.getPhoneId()); + } + } + } + + /** + * Unregisters the listener to stop receiving IMS registration changes for all phones in the + * phoneMap. + * + * @param phoneMap Map of voice capable phones mapped to the set of phones each has a compatible + * user association with. + */ + private void unregisterForImsRegistrationChanges(Map<Phone, Set<Phone>> phoneMap) { + for (Phone phone : phoneMap.keySet()) { + ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); + if (imsPhone != null) { + imsPhone.unregisterForImsRegistrationChanges(mHandler); + } + } + } + + /** + * Generates mVoiceCapablePhoneMap by iterating through {@link PhoneFactory#getPhones()} and + * checking whether each {@link Phone} corresponds to a valid and voice capable subscription. + * Maps the voice capable phones to the other voice capable phones that have compatible user + * associations + */ + private Map<Phone, Set<Phone>> generateVoiceCapablePhoneMapBasedOnUserAssociation() { + Map<Phone, Set<Phone>> voiceCapablePhoneMap = new HashMap<>(3); + + // Generate a map of phone slots that corresponds to valid and voice capable subscriptions: + Phone[] allPhones = PhoneFactory.getPhones(); + for (Phone phone : allPhones) { + int subId = phone.getSubId(); + SubscriptionInfo subInfo = + SubscriptionManagerService.getInstance().getSubscriptionInfo(subId); + + if (mFeatureFlags.dataOnlyCellularService() && + subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID && subInfo != null && + subInfo.getServiceCapabilities() + .contains(SubscriptionManager.SERVICE_CAPABILITY_VOICE)) { + Log.v(LOG_TAG, "generateVoiceCapablePhoneMapBasedOnUserAssociation: adding " + + "phoneId = " + phone.getPhoneId()); + voiceCapablePhoneMap.put(phone, new HashSet<>(3)); + } + } + + Map<Phone, Set<Phone>> userAssociationPhoneMap = new HashMap<>(3); + // Map the voice capable phones to the others that have compatible user associations: + for (Phone phone1 : voiceCapablePhoneMap.keySet()) { + Set<Phone> phone1UserAssociationCompatiblePhones = new HashSet<>(3); + for (Phone phone2 : voiceCapablePhoneMap.keySet()) { + if (phone1.getPhoneId() == phone2.getPhoneId()) { continue; } + if (phonesHaveSameUserAssociation(phone1, phone2)) { + phone1UserAssociationCompatiblePhones.add(phone2); + } + } + userAssociationPhoneMap.put(phone1, phone1UserAssociationCompatiblePhones); + } + + return userAssociationPhoneMap; + } + + private Map<Integer, Set<Phone>> generateSimultaneousCallSupportMap( + Map<Phone, Set<Phone>> phoneMap) { + Map<Integer, Set<Phone>> simultaneousCallSubSupportMap = new HashMap<>(3); + + // Initially populate simultaneousCallSubSupportMap based on the passed in phoneMap: + for (Phone phone : phoneMap.keySet()) { + simultaneousCallSubSupportMap.put(phone.getPhoneId(), + new HashSet<>(phoneMap.get(phone))); + } + + // Remove phone combinations that don't support simultaneous calling from the support map: + for (Phone phone : phoneMap.keySet()) { + if (phone.isImsRegistered()) { + if (mPhoneConfigurationManager.isVirtualDsdaEnabled() || + phone.isImsServiceSimultaneousCallingSupportCapable(mContext)) { + // Check if the transport types of each phone support simultaneous IMS calling: + int phone1TransportType = ((ImsPhone) phone.getImsPhone()).getTransportType(); + if (phone1TransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { + // The transport type of this phone is WLAN so all combos are supported: + continue; + } + for (Phone phone2 : phoneMap.keySet()) { + if (phone.getPhoneId() == phone2.getPhoneId()) { continue; } + if (!phonesSupportSimultaneousCallingViaCellularOrWlan(phone, phone2)) { + simultaneousCallSubSupportMap.get(phone.getPhoneId()).remove(phone2); + } + } + } else { + // IMS is registered, vDSDA is disabled, but IMS is not DSDA capable so + // clear the map for this phone: + simultaneousCallSubSupportMap.get(phone.getPhoneId()).clear(); + } + } else { + // Check if this phone supports simultaneous cellular calling with other phones: + for (Phone phone2 : phoneMap.keySet()) { + if (phone.getPhoneId() == phone2.getPhoneId()) { continue; } + if (!phonesSupportSimultaneousCallingViaCellularOrWlan(phone, phone2)) { + simultaneousCallSubSupportMap.get(phone.getPhoneId()).remove(phone2); + } + } + } + } + Log.v(LOG_TAG, "generateSimultaneousCallSupportMap: returning " + + "simultaneousCallSubSupportMap = " + + getStringFromMap(simultaneousCallSubSupportMap)); + return simultaneousCallSubSupportMap; + } + + /** + * Determines whether the {@link Phone} instances have compatible user associations. To be + * considered compatible based on user association, both must be associated with the same + * {@link android.os.UserHandle} or both must be unassociated. + */ + private boolean phonesHaveSameUserAssociation(Phone phone1, Phone phone2) { + return Objects.equals(phone1.getUserHandle(), phone2.getUserHandle()); + } + + private boolean phonesSupportCellularSimultaneousCalling(Phone phone1, Phone phone2) { + Set<Integer> slotsSupportingSimultaneousCellularCalls = + mPhoneConfigurationManager.getSlotsSupportingSimultaneousCellularCalls(); + Log.v(LOG_TAG, "phonesSupportCellularSimultaneousCalling: modem returned slots = " + + getStringFromSet(slotsSupportingSimultaneousCellularCalls)); + if (slotsSupportingSimultaneousCellularCalls.contains(phone1.getPhoneId()) && + slotsSupportingSimultaneousCellularCalls.contains(phone2.getPhoneId())) { + return true; + }; + return false; + } + + private boolean phonesSupportSimultaneousCallingViaCellularOrWlan(Phone phone1, Phone phone2) { + int phone2TransportType = + ((ImsPhone) phone2.getImsPhone()).getTransportType(); + return phone2TransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN || + phonesSupportCellularSimultaneousCalling(phone1, phone2); + } + + private void handleSimultaneousCallingSupportChanged() { + try { + Log.v(LOG_TAG, "handleSimultaneousCallingSupportChanged"); + // Convert mSimultaneousCallPhoneSupportMap to a map of each subId to a set of the + // subIds it supports simultaneous calling with: + Map<Integer, Set<Integer>> simultaneousCallSubscriptionIdMap = new HashMap<>(); + for (Integer phoneId : mSimultaneousCallPhoneSupportMap.keySet()) { + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone == null) { + Log.wtf(LOG_TAG, "handleSimultaneousCallingSupportChanged: phoneId=" + + phoneId + " not found."); + return; + } + int subId = phone.getSubId(); + Set<Integer> supportedSubscriptionIds = new HashSet<>(3); + for (Phone p : mSimultaneousCallPhoneSupportMap.get(phoneId)) { + supportedSubscriptionIds.add(p.getSubId()); + } + simultaneousCallSubscriptionIdMap.put(subId, supportedSubscriptionIds); + } + + // Notify listeners that simultaneous calling support has changed: + for (Listener l : mListeners) { + l.onSimultaneousCallingSupportChanged(simultaneousCallSubscriptionIdMap); + } + } catch (Exception e) { + Log.w(LOG_TAG, "handleVideoCapabilitiesChanged: Exception = " + e); + } + } + + private String getStringFromMap(Map<Integer, Set<Phone>> phoneMap) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry<Integer, Set<Phone>> entry : phoneMap.entrySet()) { + sb.append("Phone ID="); + sb.append(entry.getKey()); + sb.append(" - Simultaneous calling compatible phone IDs=["); + sb.append(entry.getValue().stream().map(Phone::getPhoneId).map(String::valueOf) + .collect(Collectors.joining(", "))); + sb.append("]; "); + } + return sb.toString(); + } + + private String getStringFromSet(Set<Integer> integerSet) { + return integerSet.stream().map(String::valueOf).collect(Collectors.joining(",")); + } +} diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java index 9f3ec3bf44..a9c2fc580d 100644 --- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java +++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java @@ -16,6 +16,7 @@ package com.android.internal.telephony.imsphone; +import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE; import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED; @@ -290,6 +291,8 @@ public class ImsPhone extends ImsPhoneBase { private final RegistrantList mSilentRedialRegistrants = new RegistrantList(); + private final RegistrantList mImsRegistrationUpdateRegistrants = new RegistrantList(); + private final LocalLog mRegLocalLog = new LocalLog(64); private TelephonyMetrics mMetrics; @@ -313,6 +316,7 @@ public class ImsPhone extends ImsPhoneBase { private @RegistrationManager.SuggestedAction int mImsRegistrationSuggestedAction; private @ImsRegistrationImplBase.ImsRegistrationTech int mImsDeregistrationTech = REGISTRATION_TECH_NONE; + private @AccessNetworkConstants.TransportType int mTransportType = TRANSPORT_TYPE_INVALID; private int mImsRegistrationCapabilities; private boolean mNotifiedRegisteredState; @@ -1664,6 +1668,14 @@ public class ImsPhone extends ImsPhoneBase { } } + public void registerForImsRegistrationChanges(Handler h, int what, Object obj) { + mImsRegistrationUpdateRegistrants.addUnique(h, what, obj); + } + + public void unregisterForImsRegistrationChanges(Handler h) { + mImsRegistrationUpdateRegistrants.remove(h); + } + @Override public void registerForSilentRedial(Handler h, int what, Object obj) { mSilentRedialRegistrants.addUnique(h, what, obj); @@ -2470,7 +2482,7 @@ public class ImsPhone extends ImsPhoneBase { int subId = getSubId(); if (SubscriptionManager.isValidSubscriptionId(subId)) { updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED, - REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE); + REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE, TRANSPORT_TYPE_INVALID); } } @@ -2478,13 +2490,13 @@ public class ImsPhone extends ImsPhoneBase { ImsRegistrationCallbackHelper.ImsRegistrationUpdate() { @Override public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) { - int imsRadioTech = attributes.getTransportType(); + int imsTransportType = attributes.getTransportType(); if (DBG) { - logd("handleImsRegistered: onImsMmTelConnected imsRadioTech=" - + AccessNetworkConstants.transportTypeToString(imsRadioTech)); + logd("handleImsRegistered: onImsMmTelConnected imsTransportType=" + + AccessNetworkConstants.transportTypeToString(imsTransportType)); } - mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsRadioTech=" - + AccessNetworkConstants.transportTypeToString(imsRadioTech)); + mRegLocalLog.log("handleImsRegistered: onImsMmTelConnected imsTransportType=" + + AccessNetworkConstants.transportTypeToString(imsTransportType)); setServiceState(ServiceState.STATE_IN_SERVICE); getDefaultPhone().setImsRegistrationState(true); mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null); @@ -2492,7 +2504,10 @@ public class ImsPhone extends ImsPhoneBase { mImsNrSaModeHandler.onImsRegistered( attributes.getRegistrationTechnology(), attributes.getFeatureTags()); updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED, - attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE); + attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE, + imsTransportType); + AsyncResult ar = new AsyncResult(null, null, null); + mImsRegistrationUpdateRegistrants.notifyRegistrants(ar); } @Override @@ -2508,6 +2523,8 @@ public class ImsPhone extends ImsPhoneBase { mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING, null); mImsStats.onImsRegistering(imsRadioTech); + AsyncResult ar = new AsyncResult(null, null, null); + mImsRegistrationUpdateRegistrants.notifyRegistrants(ar); } @Override @@ -2542,13 +2559,15 @@ public class ImsPhone extends ImsPhoneBase { } } updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED, - imsRadioTech, suggestedModemAction); + imsRadioTech, suggestedModemAction, TRANSPORT_TYPE_INVALID); if (mFeatureFlags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) { // Clear the phone number from P-Associated-Uri setCurrentSubscriberUris(null); clearPhoneNumberForSourceIms(); } + AsyncResult ar = new AsyncResult(null, null, null); + mImsRegistrationUpdateRegistrants.notifyRegistrants(ar); } @Override @@ -2688,6 +2707,11 @@ public class ImsPhone extends ImsPhoneBase { return mImsStats; } + /** Returns the {@link AccessNetworkConstants.TransportType} used to register this IMS phone. */ + public @AccessNetworkConstants.TransportType int getTransportType() { + return mTransportType; + } + /** Sets the {@link ImsStats} mock for this IMS phone during unit testing. */ @VisibleForTesting public void setImsStats(ImsStats imsStats) { @@ -2738,7 +2762,8 @@ public class ImsPhone extends ImsPhoneBase { private void updateImsRegistrationInfo( @RegistrationManager.ImsRegistrationState int regState, @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech, - @RegistrationManager.SuggestedAction int suggestedAction) { + @RegistrationManager.SuggestedAction int suggestedAction, + @AccessNetworkConstants.TransportType int transportType) { if (regState == mImsRegistrationState) { // In NOT_REGISTERED state, the current PLMN can be blocked with a suggested action. @@ -2764,6 +2789,7 @@ public class ImsPhone extends ImsPhoneBase { mDefaultPhone.mCi.updateImsRegistrationInfo(regState, imsRadioTech, 0, mImsRegistrationCapabilities, null); mImsRegistrationTech = imsRadioTech; + mTransportType = transportType; mNotifiedRegisteredState = true; return; } @@ -2771,6 +2797,7 @@ public class ImsPhone extends ImsPhoneBase { mImsRegistrationState = regState; mImsRegistrationTech = imsRadioTech; + mTransportType = transportType; mImsRegistrationSuggestedAction = suggestedAction; if (regState == REGISTRATION_STATE_NOT_REGISTERED) { mImsDeregistrationTech = imsRadioTech; diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java new file mode 100644 index 0000000000..d3fde34598 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2024 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.UserHandle; +import android.telephony.AccessNetworkConstants; +import android.telephony.ModemInfo; +import android.telephony.PhoneCapability; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyRegistryManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.telephony.flags.FeatureFlags; +import com.android.internal.telephony.imsphone.ImsPhone; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class SimultaneousCallingTrackerTest extends TelephonyTest { + // Mocked classes + Handler mHandler; + CommandsInterface mMockCi0; + CommandsInterface mMockCi1; + CommandsInterface mMockCi2; + private Phone mPhone1; // mPhone as phone 0 is already defined in TelephonyTest. + private Phone mPhone2; + private SubscriptionInfo mSubInfo; + private ImsPhone mImsPhone; + private ImsPhone mImsPhone1; + private ImsPhone mImsPhone2; + PhoneConfigurationManager.MockableInterface mMi; + private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1; + private static final PhoneCapability STATIC_DSDA_CAPABILITY; + private static final Set<Integer> STATIC_SERVICE_CAPABILITIES; + + static { + ModemInfo modemInfo1 = new ModemInfo(0, 0, true, true); + ModemInfo modemInfo2 = new ModemInfo(1, 0, true, true); + + List<ModemInfo> logicalModemList = new ArrayList<>(); + logicalModemList.add(modemInfo1); + logicalModemList.add(modemInfo2); + int[] deviceNrCapabilities = new int[0]; + + STATIC_DSDA_CAPABILITY = new PhoneCapability(2, 1, logicalModemList, false, + deviceNrCapabilities); + + STATIC_SERVICE_CAPABILITIES = new HashSet<>(1); + STATIC_SERVICE_CAPABILITIES.add(SubscriptionManager.SERVICE_CAPABILITY_VOICE); + } + PhoneConfigurationManager mPcm; + SimultaneousCallingTracker mSct; + + private FeatureFlags mFeatureFlags; + private TelephonyRegistryManager mMockRegistryManager; + + @Before + public void setUp() throws Exception { + super.setUp(getClass().getSimpleName()); + mHandler = mock(Handler.class); + mMockCi0 = mock(CommandsInterface.class); + mMockCi1 = mock(CommandsInterface.class); + mMockCi2 = mock(CommandsInterface.class); + mFeatureFlags = mock(FeatureFlags.class); + mPhone1 = mock(Phone.class); + mPhone2 = mock(Phone.class); + mImsPhone = mock(ImsPhone.class); + mImsPhone1 = mock(ImsPhone.class); + mImsPhone2 = mock(ImsPhone.class); + mSubInfo = mock(SubscriptionInfo.class); + doReturn(mImsPhone).when(mPhone).getImsPhone(); + doReturn(mImsPhone1).when(mPhone1).getImsPhone(); + doReturn(mImsPhone2).when(mPhone2).getImsPhone(); + mMi = mock(PhoneConfigurationManager.MockableInterface.class); + mPhone.mCi = mMockCi0; + mCT.mCi = mMockCi0; + mPhone1.mCi = mMockCi1; + mPhone2.mCi = mMockCi2; + doReturn(0).when(mPhone).getPhoneId(); + doReturn(10).when(mPhone).getSubId(); + doReturn(1).when(mPhone1).getPhoneId(); + // This will be updated to 11 during each test in order to trigger onSubscriptionChanged: + doReturn(110).when(mPhone1).getSubId(); + doReturn(2).when(mPhone2).getPhoneId(); + doReturn(12).when(mPhone2).getSubId(); + doReturn(STATIC_SERVICE_CAPABILITIES).when(mSubInfo).getServiceCapabilities(); + doReturn(mSubInfo).when(mSubscriptionManagerService) + .getSubscriptionInfo(any(Integer.class)); + doReturn(RIL.RADIO_HAL_VERSION_2_2).when(mMockRadioConfigProxy).getVersion(); + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + doReturn(true).when(mFeatureFlags).dataOnlyCellularService(); + mMockRegistryManager = mContext.getSystemService(TelephonyRegistryManager.class); + } + + @After + public void tearDown() throws Exception { + mPcm = null; + mSct = null; + mPhone1 = null; + mPhone2 = null; + super.tearDown(); + } + + /** + * @param numOfSim the current number of SIM subscriptions + */ + private void init(int numOfSim) throws Exception { + doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount(); + replaceInstance(SimultaneousCallingTracker.class, "sInstance", null, null); + replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null); + switch (numOfSim) { + case 0 -> mPhones = new Phone[]{}; + case 1 -> mPhones = new Phone[]{mPhone}; + case 2 -> mPhones = new Phone[]{mPhone, mPhone1}; + case 3 -> mPhones = new Phone[]{mPhone, mPhone1, mPhone2}; + } + replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); + mPcm = PhoneConfigurationManager.init(mContext, mFeatureFlags); + mSct = SimultaneousCallingTracker.init(mContext, mFeatureFlags); + replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi); + processAllMessages(); + } + + private void setRebootRequiredForConfigSwitch(boolean rebootRequired) { + doReturn(rebootRequired).when(mMi).isRebootRequiredForModemConfigChange(); + } + + private void setAndVerifyStaticCapability(PhoneCapability capability) { + mPcm.getCurrentPhoneCapability(); + ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); + verify(mMockRadioConfig).getPhoneCapability(captor.capture()); + Message msg = captor.getValue(); + AsyncResult.forMessage(msg, capability, null); + msg.sendToTarget(); + processAllMessages(); + + assertEquals(capability, mPcm.getStaticPhoneCapability()); + assertTrue(mSct.isDeviceSimultaneousCallingCapable); + + } + + private void setAndVerifySlotsSupportingSimultaneousCellularCalling(int[] enabledLogicalSlots) { + ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); + verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture()); + Message msg = captor.getValue(); + AsyncResult.forMessage(msg, enabledLogicalSlots, null); + msg.sendToTarget(); + processAllMessages(); + + HashSet<Integer> expectedSlots = new HashSet<>(); + for (int i : enabledLogicalSlots) { expectedSlots.add(i); } + assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls()); + } + + private void updateSubId(Phone phone, int newSubId) { + ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptorList = + ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class); + verify(mMockRegistryManager, times(2)) + .addOnSubscriptionsChangedListener(cBCaptorList.capture(), any()); + + // Change sub ID mapping + doReturn(newSubId).when(phone).getSubId(); + List<SubscriptionManager.OnSubscriptionsChangedListener> listeners = + cBCaptorList.getAllValues(); + listeners.get(0).onSubscriptionsChanged(); + listeners.get(1).onSubscriptionsChanged(); + processAllMessages(); + } + + /** + * Test that simultaneous calling is not supported when the device is only capable of a max + * active voice count of 1. + */ + @Test + @SmallTest + public void testDeviceNotSimultaneousCallingCapable() throws Exception { + init(1); + assertFalse(mSct.isDeviceSimultaneousCallingCapable); + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size()); + } + + /** + * Test that simultaneous calling is not supported when the subscriptions have different user + * associations. + */ + @Test + @SmallTest + public void testDifferentUserAssociations_SimultaneousCallingDisabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + + //Assign each phone to a different user which should disable simultaneous calling: + doReturn(new UserHandle(123)).when(mPhone).getUserHandle(); + doReturn(new UserHandle(321)).when(mPhone1).getUserHandle(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size()); + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size()); + } + + /** + * Test that simultaneous calling is not supported when IMS is not registered and cellular + * simultaneous calling is only supported for one SIM subscription. + */ + @Test + @SmallTest + public void testCellularDSDANotSupported_SimultaneousCallingDisabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + // Have the modem inform telephony that only phone slot 0 supports DSDA: + int[] enabledLogicalSlots = {0}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size()); + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size()); + } + + /** + * Test that simultaneous calling is supported when IMS is not registered and cellular + * simultaneous calling is supported for both SIM subscription. + */ + @Test + @SmallTest + public void testCellularDSDASupported_SimultaneousCallingEnabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11)); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10)); + } + + /** + * Test that simultaneous calling is supported when IMS is not registered and cellular + * simultaneous calling is supported for both SIM subscription. Then test that simultaneous + * calling is not supported after a multi SIM config change to single-SIM. + */ + @Test + @SmallTest + public void testSingleSimSwitch_SimultaneousCallingDisabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11)); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10)); + + // Register for multi SIM config change. + mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null); + verify(mHandler, never()).sendMessageAtTime(any(), anyLong()); + + // Switch to single sim. + setRebootRequiredForConfigSwitch(false); + mPcm.switchMultiSimConfig(1); + ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); + verify(mMockRadioConfig).setNumOfLiveModems(eq(1), captor.capture()); + + // Send message back to indicate switch success. + Message message = captor.getValue(); + AsyncResult.forMessage(message, null, null); + message.sendToTarget(); + processAllMessages(); + + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size()); + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size()); + } + + /** + * Test that simultaneous calling is not supported when IMS is registered, vDSDA is disabled, + * but ImsService#CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING is not set. + */ + @Test + @SmallTest + public void testImsDSDANotSupported_SimultaneousCallingDisabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + doReturn(true).when(mPhone).isImsRegistered(); + doReturn(true).when(mPhone1).isImsRegistered(); + doReturn(false).when(mPhone) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + doReturn(false).when(mPhone1) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(10).size()); + assertEquals(0, mSct.getSubIdsSupportingSimultaneousCalling(11).size()); + } + + /** + * Test that simultaneous calling is supported when IMS is registered, vDSDA is enabled, + * and the IMS transport type of each SIM subscription is WLAN. + */ + //TODO: Implement a way to set vDSDAEnabled to true and then re-enable this test + @Ignore + @Test + @SmallTest + public void testImsVDSDAEnabledTransportTypeWLAN_SimultaneousCallingEnabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + doReturn(true).when(mPhone).isImsRegistered(); + doReturn(true).when(mPhone1).isImsRegistered(); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone).getTransportType(); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone1).getTransportType(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11)); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10)); + } + + /** + * Test that simultaneous calling is supported when IMS is registered, vDSDA is disabled, + * ImsService#CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING is set and the IMS transport type of each + * SIM subscription is WLAN. + */ + @Test + @SmallTest + public void testImsVDSDADisabledTransportTypeWLAN_SimultaneousCallingEnabled() + throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + doReturn(true).when(mPhone).isImsRegistered(); + doReturn(true).when(mPhone1).isImsRegistered(); + doReturn(true).when(mPhone) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + doReturn(true).when(mPhone1) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone).getTransportType(); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone1).getTransportType(); + + init(2); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10).contains(11)); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11).contains(10)); + } + + /** + * Test that simultaneous calling is supported between all subs in the following 3-SIM case: + * SIM A: IMS Unregistered, vDSDA Disabled + * SIM B: IMS WWAN Registered, vDSDA Disabled, CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING not set + * SIM C: IMS WLAN Registered, vDSDA Disabled, CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING set + */ + @Test + @SmallTest + public void testThreeSimCase_SimultaneousCallingEnabled() throws Exception { + doReturn(true).when(mFeatureFlags).simultaneousCallingIndications(); + doReturn(false).when(mPhone).isImsRegistered(); + doReturn(true).when(mPhone1).isImsRegistered(); + doReturn(true).when(mPhone2).isImsRegistered(); + doReturn(true).when(mPhone1) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + doReturn(true).when(mPhone2) + .isImsServiceSimultaneousCallingSupportCapable(any(Context.class)); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mImsPhone1).getTransportType(); + doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mImsPhone2).getTransportType(); + + init(3); + setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY); + + int[] enabledLogicalSlots = {0, 1}; + setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots); + + // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot: + updateSubId(mPhone1, 11); + + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(10) + .containsAll(Arrays.asList(11,12))); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(11) + .containsAll(Arrays.asList(10,12))); + assertTrue(mSct.getSubIdsSupportingSimultaneousCalling(12) + .containsAll(Arrays.asList(10,11))); + } +} |