aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrant Menke <grantmenke@google.com>2024-02-22 18:57:47 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2024-02-22 18:57:47 +0000
commit3b0272b64aeac25f95896c06cc3c3f7181af67f7 (patch)
treea1757a5f1b0016529272db509111058c5124f1dc
parentbf371a949c7ed79c4dfeeebb273f256d7beacdcf (diff)
downloadtelephony-3b0272b64aeac25f95896c06cc3c3f7181af67f7.tar.gz
Revert^2 "Add SimultaneousCallingTracker"
bf371a949c7ed79c4dfeeebb273f256d7beacdcf Change-Id: Ia0481d9c5d245a77b3670f3d873e931d5701b0bc
-rw-r--r--src/java/com/android/internal/telephony/Phone.java15
-rw-r--r--src/java/com/android/internal/telephony/PhoneConfigurationManager.java59
-rw-r--r--src/java/com/android/internal/telephony/PhoneFactory.java5
-rw-r--r--src/java/com/android/internal/telephony/SimultaneousCallingTracker.java517
-rw-r--r--src/java/com/android/internal/telephony/imsphone/ImsPhone.java45
-rw-r--r--tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java465
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)));
+ }
+}