Refactor TelephonyConnectionService for normal call domain selection.
Bug: b/247494929
Test: atest TelephonyConnectionServiceTest. Manual Test result- go/domain-selection-testing
Change-Id: I0328d97e63d51c0bfdf47f233118c000dc5ad630
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 172407a..681b03d 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -44,6 +44,7 @@
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.CarrierConfigManager;
import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.SelectionAttributes;
import android.telephony.EmergencyRegResult;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneNumberUtils;
@@ -74,10 +75,12 @@
import com.android.internal.telephony.domainselection.DomainSelectionConnection;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.phone.FrameworksUtils;
@@ -198,6 +201,8 @@
private String mEmergencyCallId = null;
private Executor mDomainSelectionMainExecutor;
private ImsManager mImsManager = null;
+ private DomainSelectionConnection mDomainSelectionConnection;
+ private TelephonyConnection mNormalCallConnection;
/**
* Keeps track of the status of a SIM slot.
@@ -542,6 +547,27 @@
}
};
+ /**
+ * A listener for calls.
+ */
+ private final TelephonyConnection.TelephonyConnectionListener mNormalCallConnectionListener =
+ new TelephonyConnection.TelephonyConnectionListener() {
+ @Override
+ public void onStateChanged(
+ Connection connection, @Connection.ConnectionState int state) {
+ TelephonyConnection c = (TelephonyConnection) connection;
+ if (c != null) {
+ if (c.getState() == Connection.STATE_ACTIVE) {
+ Log.d(LOG_TAG, "Call State->ACTIVE."
+ + "Clearing DomainSelectionConnection");
+ c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ }
+ }
+ }
+ };
+
private final DomainSelectionConnection.DomainSelectionConnectionCallback
mEmergencyDomainSelectionConnectionCallback =
new DomainSelectionConnection.DomainSelectionConnectionCallback() {
@@ -558,6 +584,41 @@
}
};
+ private final DomainSelectionConnection.DomainSelectionConnectionCallback
+ mCallDomainSelectionConnectionCallback =
+ new DomainSelectionConnection.DomainSelectionConnectionCallback() {
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ Log.v(this, "Call domain selection terminated.");
+ if (mDomainSelectionConnection != null) {
+ mDomainSelectionConnection = null;
+ }
+
+ if (mNormalCallConnection != null) {
+ // TODO: To support ShowPreciseFailedCause,
+ // TelephonyConnection.getShowPreciseFailedCause API should be added.
+
+ // If cause is NOT_VALID then, it's a redial cancellation and use cause
+ // code from original connection.
+ com.android.internal.telephony.Connection connection =
+ mNormalCallConnection.getOriginalConnection();
+ if (cause == android.telephony.DisconnectCause.NOT_VALID) {
+ cause = connection.getDisconnectCause();
+ }
+
+ String reason = connection.getVendorDisconnectCause();
+ mNormalCallConnection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(cause, reason));
+
+ mNormalCallConnection.close();
+ mNormalCallConnection = null;
+ Log.d(this, "Call connection closed. Cause: " + cause
+ + " Reason: " + reason);
+ }
+
+ }
+ };
+
/**
* A listener to actionable events specific to the TelephonyConnection.
*/
@@ -1836,7 +1897,6 @@
}
});
}
-
final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
@@ -1878,12 +1938,15 @@
}
}
}
+ } else if (handleOutgoingCallConnection(number, connection,
+ phone, videoState)) {
+ return;
}
originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
- .setVideoState(videoState)
- .setIntentExtras(extras)
- .setRttTextStream(connection.getRttTextStream())
- .build(),
+ .setVideoState(videoState)
+ .setIntentExtras(extras)
+ .setRttTextStream(connection.getRttTextStream())
+ .build(),
// We need to wait until the phone has been chosen in GsmCdmaPhone to
// register for the associated TelephonyConnection call event listeners.
connection::registerForCallEvents);
@@ -1896,12 +1959,11 @@
handleCallStateException(e, connection, phone);
return;
}
-
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// On GSM phones, null connection means that we dialed an MMI code
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
- phone.isUtEnabled()) {
+ phone.isUtEnabled()) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: "+subId);
@@ -1934,6 +1996,99 @@
}
}
+ private void handleOutgoingCallConnectionByCallDomainSelection(
+ int domain, Phone phone, String number, int videoState) {
+ Log.d(this, "Call Domain Selected : " + domain);
+ try {
+ Bundle extras = mNormalCallConnection.getExtras();
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
+
+ if (phone != null) {
+ Log.v(LOG_TAG, "Call dialing. Domain: " + domain);
+ com.android.internal.telephony.Connection connection =
+ phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
+ .setVideoState(videoState)
+ .setIntentExtras(extras)
+ .setRttTextStream(mNormalCallConnection.getRttTextStream())
+ .build(),
+ mNormalCallConnection::registerForCallEvents);
+
+ mNormalCallConnection.setOriginalConnection(connection);
+ mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
+ return;
+ } else {
+ Log.w(this, "placeOutgoingCallConnection. Dialing failed. Phone is null");
+ mNormalCallConnection.setTelephonyConnectionDisconnected(
+ mDisconnectCauseFactory.toTelecomDisconnectCause(
+ android.telephony.DisconnectCause.OUTGOING_FAILURE,
+ "Phone is null", phone.getPhoneId()));
+ mNormalCallConnection.close();
+ }
+ } catch (CallStateException e) {
+ Log.e(this, e, "Call placeOutgoingCallConnection, phone.dial exception: " + e);
+ mNormalCallConnection.unregisterForCallEvents();
+ handleCallStateException(e, mNormalCallConnection, phone);
+ } catch (Exception e) {
+ Log.e(this, e, "Call exception in placeOutgoingCallConnection:" + e);
+ mNormalCallConnection.unregisterForCallEvents();
+ mNormalCallConnection.setTelephonyConnectionDisconnected(DisconnectCauseUtil
+ .toTelecomDisconnectCause(android.telephony.DisconnectCause.OUTGOING_FAILURE,
+ e.getMessage(), phone.getPhoneId()));
+ mNormalCallConnection.close();
+ }
+ mDomainSelectionConnection.finishSelection();
+ mDomainSelectionConnection = null;
+ mNormalCallConnection = null;
+ }
+
+ private boolean handleOutgoingCallConnection(
+ String number, TelephonyConnection connection, Phone phone, int videoState) {
+
+ if (!mDomainSelectionResolver.isDomainSelectionSupported()) {
+ return false;
+ }
+
+ String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(
+ PhoneNumberUtils.stripSeparators(number));
+ boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#"))
+ && dialPart.endsWith("#");
+ boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, phone);
+
+ // If the number is both an MMI code and a supplementary service code,
+ // it shall be treated as UT. In this case, domain selection is not performed.
+ if (isMmiCode && isSuppServiceCode) {
+ return false;
+ }
+
+ mDomainSelectionConnection = mDomainSelectionResolver
+ .getDomainSelectionConnection(phone, SELECTOR_TYPE_CALLING, false);
+ if (mDomainSelectionConnection == null) {
+ return false;
+ }
+ Log.d(LOG_TAG, "Call Connection created");
+ SelectionAttributes selectionAttributes =
+ new SelectionAttributes.Builder(phone.getPhoneId(), phone.getSubId(),
+ SELECTOR_TYPE_CALLING)
+ .setEmergency(false)
+ .setVideoCall(VideoProfile.isVideo(videoState))
+ .build();
+
+ NormalCallDomainSelectionConnection normalCallDomainSelectionConnection =
+ (NormalCallDomainSelectionConnection) mDomainSelectionConnection;
+ CompletableFuture<Integer> future = normalCallDomainSelectionConnection
+ .createNormalConnection(selectionAttributes,
+ mCallDomainSelectionConnectionCallback);
+ Log.d(LOG_TAG, "Call Domain selection triggered.");
+
+ mNormalCallConnection = connection;
+ future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection(
+ domain, phone, number, videoState), mDomainSelectionMainExecutor);
+ return true;
+ }
+
@SuppressWarnings("FutureReturnValueIgnored")
private Connection placeEmergencyConnection(
final Phone phone, final ConnectionRequest request,
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 1208ee2..a1d7405 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -42,6 +42,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
@@ -77,6 +78,7 @@
import com.android.internal.telephony.data.PhoneSwitcher;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.gsm.SuppServiceNotification;
@@ -157,6 +159,7 @@
@Mock com.android.internal.telephony.Connection mInternalConnection2;
@Mock DomainSelectionResolver mDomainSelectionResolver;
@Mock EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
+ @Mock NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
@Mock ImsPhone mImsPhone;
private EmergencyStateTracker mEmergencyStateTracker;
private Phone mPhone0;
@@ -1592,6 +1595,66 @@
assertEquals(eccCategory, dialArgs.eccCategory);
}
+ @Test
+ public void testDomainSelectionWithMmiCode() {
+ //UT domain selection should not be handled by new domain selector.
+ doNothing().when(mContext).startActivity(any());
+ setupForCallTest();
+ setupForDialForDomainSelection(mPhone0, 0, false);
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "*%2321%23", TELECOM_CALL_ID1));
+
+ verifyZeroInteractions(mNormalCallDomainSelectionConnection);
+ }
+
+ @Test
+ public void testNormalCallPsDomainSelection() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_PS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(
+ selectedDomain, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
+ @Test
+ public void testNormalCallCsDomainSelection() throws Exception {
+ setupForCallTest();
+ int selectedDomain = DOMAIN_CS;
+ setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+
+ mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+ createConnectionRequest(PHONE_ACCOUNT_HANDLE_1, "1234", TELECOM_CALL_ID1));
+
+ verify(mDomainSelectionResolver)
+ .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+ verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
+
+ ArgumentCaptor<DialArgs> argsCaptor = ArgumentCaptor.forClass(DialArgs.class);
+
+ verify(mPhone0).dial(anyString(), argsCaptor.capture(), any());
+ DialArgs dialArgs = argsCaptor.getValue();
+ assertNotNull("DialArgs param is null", dialArgs);
+ assertNotNull("intentExtras is null", dialArgs.intentExtras);
+ assertTrue(dialArgs.intentExtras.containsKey(PhoneConstants.EXTRA_DIAL_DOMAIN));
+ assertEquals(
+ selectedDomain, dialArgs.intentExtras.getInt(PhoneConstants.EXTRA_DIAL_DOMAIN, -1));
+ }
+
private void setupForDialForDomainSelection(Phone mockPhone, int domain, boolean isEmergency) {
if (isEmergency) {
doReturn(mEmergencyCallDomainSelectionConnection).when(mDomainSelectionResolver)
@@ -1600,6 +1663,13 @@
.when(mEmergencyCallDomainSelectionConnection)
.createEmergencyConnection(any(), any());
doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+ } else {
+ doReturn(mNormalCallDomainSelectionConnection).when(mDomainSelectionResolver)
+ .getDomainSelectionConnection(any(), eq(SELECTOR_TYPE_CALLING), eq(false));
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mNormalCallDomainSelectionConnection)
+ .createNormalConnection(any(), any());
+ doReturn(false).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
}
doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
@@ -1619,6 +1689,11 @@
mTestConnectionService, mEmergencyCallDomainSelectionConnection);
replaceInstance(TelephonyConnectionService.class, "mEmergencyCallId",
mTestConnectionService, TELECOM_CALL_ID1);
+ } else {
+ doReturn(CompletableFuture.completedFuture(domain))
+ .when(mNormalCallDomainSelectionConnection).reselectDomain(any());
+ replaceInstance(TelephonyConnectionService.class, "mDomainSelectionConnection",
+ mTestConnectionService, mNormalCallDomainSelectionConnection);
}
} catch (Exception e) {
// This shouldn't happen