Merge "CallLogManager & TestApp updates given business composer changes" into main
diff --git a/flags/Android.bp b/flags/Android.bp
index 3497db8..b089796 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -39,5 +39,6 @@
"telecom_bluetoothroutemanager_flags.aconfig",
"telecom_work_profile_flags.aconfig",
"telecom_connection_service_wrapper_flags.aconfig",
+ "telecom_profile_user_flags.aconfig",
],
}
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index c44bea4..ae28cc4 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -48,4 +48,5 @@
namespace: "telecom"
description: "Enables enriched calling features (e.g. Business name will show for a call)"
bug: "311688497"
+ is_exported: true
}
diff --git a/flags/telecom_profile_user_flags.aconfig b/flags/telecom_profile_user_flags.aconfig
new file mode 100644
index 0000000..c046de8
--- /dev/null
+++ b/flags/telecom_profile_user_flags.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.server.telecom.flags"
+container: "system"
+
+flag {
+ name: "profile_user_support"
+ namespace: "telecom"
+ description: "Fix issues related to the profile user like private profile"
+ bug: "326270861"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 091c8fc..d38577d 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -836,6 +836,16 @@
CallAudioState oldState = mCallAudioState;
mCallAudioState = callAudioState;
mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
+ updateAudioStateForTrackedCalls(mCallAudioState);
+ }
+
+ private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) {
+ Set<Call> calls = mCallsManager.getTrackedCalls();
+ for (Call call : calls) {
+ if (call != null && call.getConnectionService() != null) {
+ call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
+ }
+ }
}
private AudioRoute getPreferredAudioRouteFromStrategy() {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ae096af..f4b7840 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -82,7 +82,6 @@
import android.provider.BlockedNumberContract.BlockedNumbers;
import android.provider.CallLog.Calls;
import android.provider.Settings;
-import android.sysprop.TelephonyProperties;
import android.telecom.CallAttributes;
import android.telecom.CallAudioState;
import android.telecom.CallEndpoint;
@@ -165,7 +164,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -491,6 +489,7 @@
private final UserManager mUserManager;
private final CallStreamingNotification mCallStreamingNotification;
private final FeatureFlags mFeatureFlags;
+ private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
private final IncomingCallFilterGraphProvider mIncomingCallFilterGraphProvider;
@@ -610,6 +609,7 @@
CallStreamingNotification callStreamingNotification,
BluetoothDeviceManager bluetoothDeviceManager,
FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFlags,
IncomingCallFilterGraphProvider incomingCallFilterGraphProvider) {
mContext = context;
@@ -717,6 +717,7 @@
mCallStreamingController = new CallStreamingController(mContext, mLock);
mCallStreamingNotification = callStreamingNotification;
mFeatureFlags = featureFlags;
+ mTelephonyFeatureFlags = telephonyFlags;
if (mFeatureFlags.useImprovedListenerOrder()) {
mListeners.add(mInCallController);
@@ -3428,8 +3429,14 @@
// then include only that SIM based PhoneAccount and any non-SIM PhoneAccounts, such as SIP.
@VisibleForTesting
public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
- boolean isVideo, boolean isEmergency) {
- return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
+ boolean isVideo, boolean isEmergency, boolean isConference) {
+ if (mTelephonyFeatureFlags.simultaneousCallingIndications()) {
+ return constructPossiblePhoneAccountsNew(handle, user, isVideo, isEmergency,
+ isConference);
+ } else {
+ return constructPossiblePhoneAccountsOld(handle, user, isVideo, isEmergency,
+ isConference);
+ }
}
// Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
@@ -3444,7 +3451,7 @@
}
}
- public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
+ private List<PhoneAccountHandle> constructPossiblePhoneAccountsOld(Uri handle, UserHandle user,
boolean isVideo, boolean isEmergency, boolean isConference) {
if (handle == null) {
@@ -3485,6 +3492,82 @@
return allAccounts;
}
+ /**
+ * Filters the list of all PhoneAccounts that match the outgoing call Handle's schema against
+ * the outgoing call request criteria and the state of the already ongoing calls on the
+ * device and their potential simultaneous calling restrictions.
+ * @return The filtered List
+ */
+ private List<PhoneAccountHandle> constructPossiblePhoneAccountsNew(Uri handle, UserHandle user,
+ boolean isVideo, boolean isEmergency, boolean isConference) {
+ if (handle == null) {
+ return Collections.emptyList();
+ }
+ // If we're specifically looking for video capable accounts, then include that capability,
+ // otherwise specify no additional capability constraints. When handling the emergency call,
+ // it also needs to find the phone accounts excluded by CAPABILITY_EMERGENCY_CALLS_ONLY.
+ int capabilities = isVideo ? PhoneAccount.CAPABILITY_VIDEO_CALLING : 0;
+ capabilities |= isConference ? PhoneAccount.CAPABILITY_ADHOC_CONFERENCE_CALLING : 0;
+ List<PhoneAccountHandle> allAccounts =
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
+ capabilities,
+ isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
+ isEmergency);
+ Log.v(this, "constructPossiblePhoneAccountsNew: allAccounts=" + allAccounts);
+ Set<PhoneAccountHandle> activeCallAccounts = mCalls.stream()
+ .filter(c -> !c.isDisconnected() && !c.isNew()).map(Call::getTargetPhoneAccount)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Log.v(this, "constructPossiblePhoneAccountsNew: activeCallAccounts="
+ + activeCallAccounts);
+ // No Active calls - all accounts are valid
+ if (activeCallAccounts.isEmpty()) return allAccounts;
+ // The emergency call should be attempted only over the same SIM PhoneAccounts where there
+ // are already ongoing calls - filter out inactive SIM PhoneAccounts in this case.
+ if (isEmergency) {
+ Set<PhoneAccountHandle> simAccounts =
+ new HashSet<>(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser());
+ if (activeCallAccounts.stream().anyMatch(simAccounts::contains)) {
+ allAccounts.removeIf(h -> {
+ boolean isRemoved = simAccounts.contains(h) && !activeCallAccounts.contains(h);
+ if (isRemoved) {
+ Log.i(this, "constructPossiblePhoneAccountsNew: removing candidate PAH ["
+ + h + "] because another SIM account is active with an emergency "
+ + "call");
+ }
+ return isRemoved;
+ });
+ }
+ }
+ // Apply restrictions to which PhoneAccounts can be used to place a call by looking at
+ // active calls and removing candidate PhoneAccounts if they are from the same source
+ // as the active call and the candidate PhoneAccount is not part of the restriction.
+ for (PhoneAccountHandle callHandle : activeCallAccounts) {
+ allAccounts.removeIf(candidateHandle -> {
+ PhoneAccount callAcct = mPhoneAccountRegistrar.getPhoneAccount(callHandle,
+ user);
+ if (callAcct == null) {
+ Log.w(this, "constructPossiblePhoneAccountsNew: unexpected"
+ + "null PA for PAH, removing : " + candidateHandle);
+ return true;
+ }
+ boolean isRemoved = !Objects.equals(candidateHandle, callHandle)
+ && Objects.equals(candidateHandle.getComponentName(),
+ callHandle.getComponentName())
+ && callAcct.hasSimultaneousCallingRestriction()
+ && !callAcct.getSimultaneousCallingRestriction().contains(candidateHandle);
+ if (isRemoved) {
+ Log.i(this, "constructPossiblePhoneAccountsNew: removing candidate"
+ + " PAH [" + candidateHandle + "] because it is not part of the"
+ + " restriction set by [" + callHandle + "], restriction="
+ + callAcct.getSimultaneousCallingRestriction());
+ }
+ return isRemoved;
+ });
+ }
+ return allAccounts;
+ }
+
private TelephonyManager getTelephonyManager() {
return mContext.getSystemService(TelephonyManager.class);
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 55f48e3..0ec3209 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -104,8 +104,6 @@
public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
"Exception thrown while setting the in-call adapter.";
- private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
-
@VisibleForTesting
public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
mAnomalyReporter = mAnomalyReporterAdapter;
@@ -1296,12 +1294,6 @@
userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
mFeatureFlags = featureFlags;
- if (telephonyFeatureFlags != null) {
- mTelephonyFeatureFlags = telephonyFeatureFlags;
- } else {
- mTelephonyFeatureFlags =
- new com.android.internal.telephony.flags.FeatureFlagsImpl();
- }
}
private void restrictPhoneCallOps() {
@@ -2030,9 +2022,9 @@
public void bindToServices(Call call, boolean skipBTServices) {
UserHandle userFromCall = getUserFromCall(call);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ UserHandle parentUser = mFeatureFlags.profileUserSupport()
? um.getProfileParent(userFromCall) : null;
- if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ if (!mFeatureFlags.profileUserSupport()
&& um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
}
@@ -2111,10 +2103,10 @@
UserHandle userFromCall = getUserFromCall(call);
UserManager um = mContext.getSystemService(UserManager.class);
- UserHandle parentUser = mTelephonyFeatureFlags.workProfileApiSplit()
+ UserHandle parentUser = mFeatureFlags.profileUserSupport()
? um.getProfileParent(userFromCall) : null;
- if (!mTelephonyFeatureFlags.workProfileApiSplit()
+ if (!mFeatureFlags.profileUserSupport()
&& um.isManagedProfile(userFromCall.getIdentifier())) {
parentUser = um.getProfileParent(userFromCall);
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index b4c3a4d..1d0300d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -230,7 +230,8 @@
Executor asyncTaskExecutor,
Executor asyncCallAudioTaskExecutor,
BlockedNumbersAdapter blockedNumbersAdapter,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ com.android.internal.telephony.flags.FeatureFlags telephonyFlags) {
mContext = context.getApplicationContext();
mFeatureFlags = featureFlags;
LogUtils.initLogging(mContext);
@@ -427,6 +428,7 @@
callStreamingNotification,
bluetoothDeviceManager,
featureFlags,
+ telephonyFlags,
IncomingCallFilterGraph::new);
mIncomingCallNotifier = incomingCallNotifier;
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 24e5d57..845f788 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -234,7 +234,8 @@
showNotification);
}
},
- new FeatureFlagsImpl()));
+ new FeatureFlagsImpl(),
+ new com.android.internal.telephony.flags.FeatureFlagsImpl()));
}
}
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index 76d83cc..299bcc3 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -81,12 +81,17 @@
String transactionName) {
Log.i(TAG, String.format("transaction %s completed: with result=[%d]",
transactionName, result.getResult()));
- if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
- receiver.onResult(result);
- } else {
- receiver.onError(
- new CallException(result.getMessage(),
- result.getResult()));
+ try {
+ if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+ receiver.onResult(result);
+ } else {
+ receiver.onError(
+ new CallException(result.getMessage(),
+ result.getResult()));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, String.format("onTransactionCompleted: Notifying transaction result"
+ + " %s resulted in an Exception.", result), e);
}
finishTransaction();
}
@@ -94,8 +99,13 @@
@Override
public void onTransactionTimeout(String transactionName){
Log.i(TAG, String.format("transaction %s timeout", transactionName));
- receiver.onError(new CallException(transactionName + " timeout",
- CODE_OPERATION_TIMED_OUT));
+ try {
+ receiver.onError(new CallException(transactionName + " timeout",
+ CODE_OPERATION_TIMED_OUT));
+ } catch (Exception e) {
+ Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
+ + " %s resulted in an Exception.", transactionName), e);
+ }
finishTransaction();
}
});
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 1529629..cb9aba9 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -78,13 +78,13 @@
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneCapability;
import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.Pair;
import android.widget.Toast;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
@@ -103,7 +103,6 @@
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.CreateConnectionResponse;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
@@ -112,7 +111,6 @@
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallController;
import com.android.server.telecom.InCallControllerFactory;
-import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.InCallTonePlayer;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
@@ -140,6 +138,8 @@
import com.android.server.telecom.ui.ToastFactory;
import com.android.server.telecom.voip.TransactionManager;
+import com.google.common.base.Objects;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -148,7 +148,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
-import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -158,10 +157,12 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
@RunWith(JUnit4.class)
public class CallsManagerTest extends TelecomTestCase {
@@ -177,6 +178,10 @@
new UserHandle(SECONDARY_USER_ID));
private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.foo/.Blah"), "Sim2");
+ private static final PhoneAccountHandle SIM_3_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.foo/.Blah"), "Sim3");
+ private static final PhoneAccountHandle CALL_PROVIDER_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.sip.foo/.Blah"), "sip1");
private static final PhoneAccountHandle CONNECTION_MGR_1_HANDLE = new PhoneAccountHandle(
ComponentName.unflattenFromString("com.bar/.Conn"), "Cm1");
private static final PhoneAccountHandle CONNECTION_MGR_2_HANDLE = new PhoneAccountHandle(
@@ -210,6 +215,18 @@
| PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
.setIsEnabled(true)
.build();
+ private static final PhoneAccount SIM_3_ACCOUNT = new PhoneAccount.Builder(SIM_3_HANDLE, "Sim3")
+ .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+ .setIsEnabled(true)
+ .build();
+ private static final PhoneAccount CALL_PROVIDER_ACCOUNT =
+ new PhoneAccount.Builder(CALL_PROVIDER_HANDLE, "sip1")
+ .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
+ | PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
+ .setIsEnabled(true)
+ .build();
private static final PhoneAccount SELF_MANAGED_ACCOUNT = new PhoneAccount.Builder(
SELF_MANAGED_HANDLE, "Self")
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
@@ -293,6 +310,7 @@
@Mock private CallStreamingNotification mCallStreamingNotification;
@Mock private BluetoothDeviceManager mBluetoothDeviceManager;
@Mock private FeatureFlags mFeatureFlags;
+ @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFlags;
@Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
private CallsManager mCallsManager;
@@ -370,6 +388,7 @@
mCallStreamingNotification,
mBluetoothDeviceManager,
mFeatureFlags,
+ mTelephonyFlags,
(call, listener, context, timeoutsAdapter, lock) -> mIncomingCallFilterGraph);
when(mPhoneAccountRegistrar.getPhoneAccount(
@@ -379,6 +398,10 @@
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
when(mPhoneAccountRegistrar.getPhoneAccount(
+ eq(SIM_3_HANDLE), any())).thenReturn(SIM_3_ACCOUNT);
+ when(mPhoneAccountRegistrar.getPhoneAccount(
+ eq(CALL_PROVIDER_HANDLE), any())).thenReturn(CALL_PROVIDER_ACCOUNT);
+ when(mPhoneAccountRegistrar.getPhoneAccount(
eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
@@ -394,8 +417,21 @@
@MediumTest
@Test
public void testConstructPossiblePhoneAccounts() throws Exception {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(false);
+ setupMsimAccounts();
// Should be empty since the URI is null.
- assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
+ assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null,
+ false, false, false).size());
+ }
+
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccounts_simulCalling() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupMsimAccounts();
+ // Should be empty since the URI is null.
+ assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null,
+ false, false, false).size());
}
private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
@@ -419,6 +455,7 @@
ongoingCall.setState(CallState.ACTIVE, "just cuz");
return ongoingCall;
}
+
/**
* Verify behavior for multisim devices where we want to ensure that the active sim is used for
* placing a new call.
@@ -427,32 +464,127 @@
@MediumTest
@Test
public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(false);
setupMsimAccounts();
Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false, false);
+ TEST_ADDRESS, null, false, false, false);
assertEquals(1, phoneAccountHandles.size());
assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
}
/**
+ * Verify behavior for multisim devices where we want to ensure that the active sim is used for
+ * placing a new call when a restriction is set as well as other call providers from different
+ * apps.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCallingRestriction() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ CALL_PROVIDER_HANDLE), Collections.emptySet());
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(Arrays.asList(SIM_2_HANDLE, CALL_PROVIDER_HANDLE), phoneAccountHandles);
+ }
+
+ /**
+ * When we have 3 SIM PhoneAccounts on a device, but only 2 allow simultaneous calling, place a
+ * call on a SIM that allows simultaneous calling and verify that the subset of PhoneAccounts
+ * are available when in a call as well as the call provider.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCallingRestrictionSubset() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE, SIM_3_HANDLE,
+ CALL_PROVIDER_HANDLE), new ArraySet<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE, CALL_PROVIDER_HANDLE),
+ phoneAccountHandles);
+ }
+
+ /**
+ * When we have 3 SIM PhoneAccounts on a device, but only 2 allow simultaneous calling, place a
+ * call on the SIM that does not allow simultaneous calling and verify that only that SIM and
+ * the separate call provider are allowed to place a second call.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCallingRestrictionSubset2() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE, SIM_3_HANDLE,
+ CALL_PROVIDER_HANDLE), new ArraySet<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+ Call ongoingCall = constructOngoingCall("1", SIM_3_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(Arrays.asList(SIM_3_HANDLE, CALL_PROVIDER_HANDLE),
+ phoneAccountHandles);
+ }
+
+ /**
* Verify behavior for multisim devices when there are no calls active; expect both accounts.
* @throws Exception
*/
@MediumTest
@Test
public void testConstructPossiblePhoneAccountsMultiSimIdle() throws Exception {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(false);
setupMsimAccounts();
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false, false);
+ TEST_ADDRESS, null, false, false, false);
assertEquals(2, phoneAccountHandles.size());
}
/**
+ * Verify behavior for multisim devices when there are no calls active and there are no calling
+ * restrictions set; expect both accounts.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimIdle_noSimulCallingRestriction() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsNoSimultaneousCallingRestriction();
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(3, phoneAccountHandles.size());
+ }
+
+ /**
+ * Verify behavior for multisim devices when there are no calls active and there are no calling
+ * restrictions set; expect both accounts.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimIdle_withSimulCallingRestriction() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ CALL_PROVIDER_HANDLE), Collections.emptySet());
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(3, phoneAccountHandles.size());
+ }
+
+ /**
* For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
* PhoneAccountHandles are constructed while placing a new call.
* @throws Exception
@@ -461,6 +593,7 @@
@Test
public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
Exception {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(false);
setupMsimAccounts();
setMaxActiveVoiceSubscriptions(2);
@@ -468,11 +601,29 @@
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false, false);
+ TEST_ADDRESS, null, false, false, false);
assertEquals(2, phoneAccountHandles.size());
}
/**
+ * For multisim devices with an ongoing call, verify that all call capable PhoneAccounts are
+ * available when creating a second call.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCalling_dsdaPossible() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsNoSimultaneousCallingRestriction();
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false, false);
+ assertEquals(3, phoneAccountHandles.size());
+ }
+
+ /**
* For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
* PhoneAccountHandle is constructed while placing an emergency call.
* @throws Exception
@@ -481,6 +632,7 @@
@Test
public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
throws Exception {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(false);
setupMsimAccounts();
setMaxActiveVoiceSubscriptions(2);
@@ -488,11 +640,96 @@
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
- TEST_ADDRESS, null, false, true /* isEmergency */);
+ TEST_ADDRESS, null, false, true /* isEmergency */, false);
assertEquals(1, phoneAccountHandles.size());
assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
}
+ /**
+ * For multisim devices with an ongoing call, verify that only the active SIM's
+ * PhoneAccountHandle is available if we have a calling restriction where only one SIM is
+ * active at a time.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCalling_emergencyCall() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ CALL_PROVIDER_HANDLE), Collections.emptySet());
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, true /* isEmergency */, false);
+ assertEquals(2, phoneAccountHandles.size());
+ assertTrue("Candidate PAHs must contain the SIM account hosting the emergency call",
+ phoneAccountHandles.contains(SIM_2_HANDLE));
+ assertFalse("Candidate PAHs must not contain other SIM accounts",
+ phoneAccountHandles.contains(SIM_1_HANDLE));
+ }
+
+ /**
+ * For devices with an ongoing call on a call provider, verify that when an emergency
+ * call is placed, all SIM accounts are still available for SIM selection.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccounts_callProvider_emergencyCall() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ CALL_PROVIDER_HANDLE), Collections.emptySet());
+
+ Call ongoingCall = constructOngoingCall("1", CALL_PROVIDER_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, true /* isEmergency */, false);
+ assertEquals(3, phoneAccountHandles.size());
+ }
+
+ /**
+ * For multisim devices with an ongoing call, for backwards compatibility, only allow the
+ * SIM with the active call to be chosen to place an emergency call, even if there is no
+ * calling restriction.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_simulCallingRest_emergencyCall() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsNoSimultaneousCallingRestriction();
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, true /* isEmergency */, false);
+ assertEquals(2, phoneAccountHandles.size());
+ assertTrue("Candidate PAHs must contain the SIM account hosting the emergency call",
+ phoneAccountHandles.contains(SIM_2_HANDLE));
+ assertFalse("Candidate PAHs must not contain other SIM accounts",
+ phoneAccountHandles.contains(SIM_1_HANDLE));
+ }
+
+ /**
+ * For multisim devices with an ongoing call on a call provider, it is still possible to place
+ * a SIM call on any SIM account.
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccounts_crossAccount_simulCalling() {
+ when(mTelephonyFlags.simultaneousCallingIndications()).thenReturn(true);
+ setupAccountsWithCallingRestriction(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ CALL_PROVIDER_HANDLE), Collections.emptySet());
+
+ Call ongoingCall = constructOngoingCall("1", CALL_PROVIDER_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false /* isEmergency */, false);
+ assertEquals(3, phoneAccountHandles.size());
+ }
+
private void setupCallerInfoLookupHelper() {
doAnswer(invocation -> {
Uri handle = invocation.getArgument(0);
@@ -3428,6 +3665,9 @@
callback.onRequestFocusDone(call);
}
+ /**
+ * Set up 2 SIM accounts in DSDS mode, where only one SIM can be active at a time for calls.
+ */
private void setupMsimAccounts() {
TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
when(mockTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims()).thenReturn(1);
@@ -3438,6 +3678,54 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
}
+ /**
+ * Set up 2 SIM accounts in DSDS mode and one call provider, where there is no restriction on
+ * simultaneous calls across accounts.
+ */
+ private void setupAccountsNoSimultaneousCallingRestriction() {
+ when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+ any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+ new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE, CALL_PROVIDER_HANDLE)));
+ when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
+ new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+ }
+
+ /**
+ * Set up the call capable PhoneAccounts passed in here to have simultaneous calling
+ * restrictions. If the callCapableHandle is a SIM account and it is in the restriction set, we
+ * will set that callCapableHandle's restriction to the Set. If not, we will set the restriction
+ * to allow no other simultaneous calls.
+ */
+ private void setupAccountsWithCallingRestriction(List<PhoneAccountHandle> callCapableHandles,
+ Set<PhoneAccountHandle> restrictions) {
+ when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+ any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+ new ArrayList<>(callCapableHandles));
+ List<PhoneAccountHandle> allSims = Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE,
+ SIM_3_HANDLE);
+ List<PhoneAccountHandle> simsToTest = callCapableHandles.stream()
+ .filter(allSims::contains).toList();
+ when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(simsToTest);
+ // Remap the PhoneAccounts to inherit the restriction set
+ for (PhoneAccountHandle callCapableHandle : callCapableHandles) {
+ PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccount(
+ callCapableHandle, callCapableHandle.getUserHandle());
+ assertNotNull("test setup error: could not find PA for PAH:" + callCapableHandle,
+ pa);
+ // For simplicity, for testing only apply restrictions to SIM accounts
+ if (!allSims.contains(callCapableHandle)) continue;
+ if (restrictions.contains(callCapableHandle)) {
+ pa = new PhoneAccount.Builder(pa)
+ .setSimultaneousCallingRestriction(restrictions).build();
+ } else {
+ pa = new PhoneAccount.Builder(pa)
+ .setSimultaneousCallingRestriction(Collections.emptySet()).build();
+ }
+ when(mPhoneAccountRegistrar.getPhoneAccount(eq(callCapableHandle),
+ any())).thenReturn(pa);
+ }
+ }
+
private void setMaxActiveVoiceSubscriptions(int num) {
TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 24b14de..3d99d07 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -208,7 +208,6 @@
private UserHandle mChildUserHandle = UserHandle.of(10);
private @Mock Call mMockChildUserCall;
private UserHandle mParentUserHandle = UserHandle.of(1);
- private @Mock com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
@Override
@Before
@@ -239,11 +238,9 @@
mMockPermissionInfo);
when(mMockContext.getAttributionSource()).thenReturn(new AttributionSource(Process.myUid(),
"com.android.server.telecom.tests", null));
- when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
- mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags,
- mTelephonyFeatureFlags);
+ mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags);
// Capture the broadcast receiver registered.
doAnswer(invocation -> {
mRegisteredReceiver = invocation.getArgument(0);
@@ -319,6 +316,7 @@
when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
when(mMockUserInfo.isManagedProfile()).thenReturn(true);
when(mFeatureFlags.separatelyBindToBtIncallService()).thenReturn(false);
+ when(mFeatureFlags.profileUserSupport()).thenReturn(false);
}
@Override
@@ -1941,7 +1939,7 @@
mMockChildUserInfo);
when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
false);
- when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
+ when(mFeatureFlags.profileUserSupport()).thenReturn(true);
}
@Test
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index b7de84f..57802e3 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -221,6 +221,8 @@
CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
@Mock
FeatureFlags mFeatureFlags;
+ @Mock
+ com.android.internal.telephony.flags.FeatureFlags mTelephonyFlags;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -581,7 +583,8 @@
Runnable::run,
Runnable::run,
mBlockedNumbersAdapter,
- mFeatureFlags);
+ mFeatureFlags,
+ mTelephonyFlags);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index 0a7e27d..b7848a2 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -56,6 +56,7 @@
public static final int SUCCESS = 0;
public static final int FAILED = 1;
public static final int TIMEOUT = 2;
+ public static final int EXCEPTION = 3;
private long mSleepTime;
private String mName;
@@ -70,6 +71,10 @@
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ if (mType == EXCEPTION) {
+ mLog.append(mName).append(" exception;\n");
+ throw new IllegalStateException("TEST EXCEPTION");
+ }
CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
mHandler.postDelayed(() -> {
if (mType == SUCCESS) {
@@ -246,8 +251,89 @@
public void onError(CallException e) {
exceptionFuture.complete(e.getMessage());
}
- }; mTransactionManager.addTransaction(t, outcomeReceiver);
+ };
+ mTransactionManager.addTransaction(t, outcomeReceiver);
String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
assertTrue(message.contains("timeout"));
}
+
+ @SmallTest
+ @Test
+ public void testTransactionException()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+ TestVoipCallTransaction.EXCEPTION);
+ VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ }
+
+ @Override
+ public void onError(CallException e) {
+ exceptionFuture.complete(e.getMessage());
+ }
+ };
+ mTransactionManager.addTransaction(t1, outcomeExceptionReceiver);
+ // Transaction will timeout because the Exception caused the transaction to stop processing.
+ exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+ assertTrue(mLog.toString().contains("t1 exception;\n"));
+ // Verify an exception in a processing a previous transaction does not stall the next one.
+ CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ resultFuture::complete;
+ mTransactionManager.addTransaction(t2, outcomeReceiver);
+ String expectedLog = "t1 exception;\nt2 success;\n";
+ assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+ assertEquals(expectedLog, mLog.toString());
+ }
+
+ @SmallTest
+ @Test
+ public void testTransactionResultException()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ throw new IllegalStateException("RESULT EXCEPTION");
+ }
+
+ @Override
+ public void onError(CallException e) {
+ }
+ };
+ mTransactionManager.addTransaction(t1, outcomeExceptionReceiver);
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeException2Receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ }
+
+ @Override
+ public void onError(CallException e) {
+ throw new IllegalStateException("RESULT EXCEPTION");
+ }
+ };
+ mTransactionManager.addTransaction(t2, outcomeException2Receiver);
+ // Verify an exception in a previous transaction result does not stall the next one.
+ CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ resultFuture::complete;
+ mTransactionManager.addTransaction(t3, outcomeReceiver);
+ String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
+ assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+ assertEquals(expectedLog, mLog.toString());
+ }
}