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