Reject incoming call for a normal routing emergency call

An emergency call fails when receiving a call at the same time.
Reject incoming call before dialing a normal routing emergency call
in ringing state.

Bug: 339159523
Test: atest TelephonyConnectionServiceTest
Manual test:
1. Make a call from A to DUT.
2. Check whether the call is in alerting state in A.
3. At that moment, dial a normal routing emergency number in DUT
   before the incoming call is notified.

Change-Id: Ice2bf79d55cee2e8dc00c5ed73f7006726078a2a
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 1386dce..565393d 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -235,6 +235,7 @@
     private DomainSelectionResolver mDomainSelectionResolver;
     private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
     private TelephonyConnection mEmergencyConnection;
+    private TelephonyConnection mNormalRoutingEmergencyConnection;
     private Executor mDomainSelectionMainExecutor;
     private ImsManager mImsManager = null;
     private DomainSelectionConnection mDomainSelectionConnection;
@@ -565,6 +566,22 @@
     }
 
     /**
+     * A listener for normal routing emergency calls.
+     */
+    private final TelephonyConnection.TelephonyConnectionListener
+            mNormalRoutingEmergencyConnectionListener =
+                    new TelephonyConnection.TelephonyConnectionListener() {
+                @Override
+                public void onStateChanged(Connection connection,
+                        @Connection.ConnectionState int state) {
+                    TelephonyConnection c = (TelephonyConnection) connection;
+                    Log.i(this, "onStateChanged normal routing callId=" + c.getTelecomCallId()
+                            + ", state=" + state);
+                    mEmergencyStateTracker.onNormalRoutingEmergencyCallStateChanged(c, state);
+                }
+            };
+
+    /**
      * A listener for emergency calls.
      */
     private final TelephonyConnection.TelephonyConnectionListener mEmergencyConnectionListener =
@@ -2262,12 +2279,48 @@
                         }
                     }
                     if (mDomainSelectionResolver.isDomainSelectionSupported()) {
-                        if (isNormalRouting(phone, number)
-                                    && handleOutgoingCallConnection(number, connection,
-                                            phone, videoState)) {
+                        if (isNormalRouting(phone, number)) {
                             /** Normal routing emergency number shall be handled
                              * by normal call domain selctor.*/
                             Log.i(this, "placeOutgoingConnection normal routing number");
+                            mNormalRoutingEmergencyConnection = connection;
+                            mEmergencyStateTracker.startNormalRoutingEmergencyCall(
+                                    phone, connection, result -> {
+                                        Log.i(this, "placeOutgoingConnection normal routing number:"
+                                                + " result = " + result);
+                                        if (connection.getState()
+                                                == Connection.STATE_DISCONNECTED) {
+                                            Log.i(this, "placeOutgoingConnection "
+                                                    + "reject incoming, dialing canceled");
+                                            return;
+                                        }
+                                        if (!handleOutgoingCallConnection(number, connection,
+                                                phone, videoState)) {
+                                            Log.w(this, "placeOriginalConnection - Unexpected, "
+                                                    + "domain selector not available.");
+                                            // Notify EmergencyStateTracker to reset the state.
+                                            onLocalHangup(connection);
+                                            // Try dialing without domain selection
+                                            // as a best-effort.
+                                            try {
+                                                // EmergencyStateTracker ensures this is
+                                                // on the main thread.
+                                                connection.setOriginalConnection(phone.dial(number,
+                                                        new ImsPhone.ImsDialArgs.Builder()
+                                                        .setVideoState(videoState)
+                                                        .setIntentExtras(extras)
+                                                        .setRttTextStream(
+                                                                connection.getRttTextStream())
+                                                        .build(),
+                                                        connection::registerForCallEvents));
+                                            } catch (CallStateException e) {
+                                                connection.unregisterForCallEvents();
+                                                handleCallStateException(e, connection, phone);
+                                            }
+                                        }
+                                    });
+                            connection.addTelephonyConnectionListener(
+                                    mNormalRoutingEmergencyConnectionListener);
                             return;
                         }
                     }
@@ -2341,6 +2394,31 @@
     }
 
     private void handleOutgoingCallConnectionByCallDomainSelection(
+            int domain, Phone phone, String number, int videoState,
+            TelephonyConnection connection) {
+        if (mNormalRoutingEmergencyConnection == connection) {
+            CompletableFuture<Void> rejectFuture = checkAndRejectIncomingCall(phone, (ret) -> {
+                if (!ret) {
+                    Log.i(this, "handleOutgoingCallConnectionByCallDomainSelection "
+                            + "reject incoming call failed");
+                }
+            });
+            CompletableFuture<Void> unused = rejectFuture.thenRun(() -> {
+                if (connection.getState() == Connection.STATE_DISCONNECTED) {
+                    Log.i(this, "handleOutgoingCallConnectionByCallDomainSelection "
+                            + "reject incoming, dialing canceled");
+                    return;
+                }
+                handleOutgoingCallConnectionByCallDomainSelection(
+                        domain, phone, number, videoState);
+            });
+            return;
+        }
+
+        handleOutgoingCallConnectionByCallDomainSelection(domain, phone, number, videoState);
+    }
+
+    private void handleOutgoingCallConnectionByCallDomainSelection(
             int domain, Phone phone, String number, int videoState) {
         Log.d(this, "Call Domain Selected : " + domain);
         try {
@@ -2442,7 +2520,7 @@
             Log.d(LOG_TAG, "Selecting same domain as ongoing call on same subId");
             mNormalCallConnection = connection;
             handleOutgoingCallConnectionByCallDomainSelection(
-                    activeCallDomain, phone, number, videoState);
+                    activeCallDomain, phone, number, videoState, connection);
             return true;
         }
 
@@ -2469,7 +2547,7 @@
 
         mNormalCallConnection = connection;
         future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection(
-                domain, phone, number, videoState), mDomainSelectionMainExecutor);
+                domain, phone, number, videoState, connection), mDomainSelectionMainExecutor);
 
         if (isPotentialUssdCode) {
             Log.v(LOG_TAG, "PotentialUssdCode. Closing connection with DisconnectCause.DIALED_MMI");
@@ -2608,8 +2686,14 @@
                     Log.i(this, "createEmergencyConnection reject incoming call failed");
                 }
             });
-            rejectFuture.thenRun(() -> placeEmergencyConnectionOnSelectedDomain(request,
-                    resultConnection, phone));
+            rejectFuture.thenRun(() -> {
+                if (resultConnection.getState() == Connection.STATE_DISCONNECTED) {
+                    Log.i(this, "createEmergencyConnection "
+                            + "reject incoming, dialing canceled");
+                    return;
+                }
+                placeEmergencyConnectionOnSelectedDomain(request, resultConnection, phone);
+            });
         }, mDomainSelectionMainExecutor);
     }
 
@@ -2629,8 +2713,14 @@
                             Log.i(this, "dialCsEmergencyCall reject incoming call failed");
                         }
                     });
-                    future.thenRun(() -> placeEmergencyConnectionOnSelectedDomain(request,
-                            resultConnection, phone));
+                    CompletableFuture<Void> unused = future.thenRun(() -> {
+                        if (resultConnection.getState() == Connection.STATE_DISCONNECTED) {
+                            Log.i(this, "dialCsEmergencyCall "
+                                    + "reject incoming, dialing canceled");
+                            return;
+                        }
+                        placeEmergencyConnectionOnSelectedDomain(request, resultConnection, phone);
+                    });
                 });
     }
 
@@ -2908,7 +2998,7 @@
                 mNormalCallConnection = c;
 
                 future.thenAcceptAsync((result) -> {
-                    onNormalCallRedial(c, phone, result, videoState);
+                    onNormalCallRedial(phone, result, videoState, c);
                 }, mDomainSelectionMainExecutor);
                 return true;
             }
@@ -2934,7 +3024,14 @@
                 Log.i(this, "onEmergencyRedialOnDomain reject incoming call failed");
             }
         });
-        future.thenRun(() -> onEmergencyRedialOnDomainInternal(connection, phone, extras));
+        CompletableFuture<Void> unused = future.thenRun(() -> {
+            if (connection.getState() == Connection.STATE_DISCONNECTED) {
+                Log.i(this, "onEmergencyRedialOnDomain "
+                        + "reject incoming, dialing canceled");
+                return;
+            }
+            onEmergencyRedialOnDomainInternal(connection, phone, extras);
+        });
     }
 
     private void onEmergencyRedialOnDomainInternal(TelephonyConnection connection,
@@ -3100,6 +3197,28 @@
         onEmergencyRedialOnDomain(connection, phone, result);
     }
 
+    private void onNormalCallRedial(Phone phone, @NetworkRegistrationInfo.Domain int domain,
+            int videoState, TelephonyConnection connection) {
+        if (mNormalRoutingEmergencyConnection == connection) {
+            CompletableFuture<Void> rejectFuture = checkAndRejectIncomingCall(phone, (ret) -> {
+                if (!ret) {
+                    Log.i(this, "onNormalCallRedial reject incoming call failed");
+                }
+            });
+            CompletableFuture<Void> unused = rejectFuture.thenRun(() -> {
+                if (connection.getState() == Connection.STATE_DISCONNECTED) {
+                    Log.i(this, "onNormalCallRedial "
+                            + "reject incoming, dialing canceled");
+                    return;
+                }
+                onNormalCallRedial(connection, phone, domain, videoState);
+            });
+            return;
+        }
+
+        onNormalCallRedial(connection, phone, domain, videoState);
+    }
+
     private void onNormalCallRedial(TelephonyConnection connection, Phone phone,
             @NetworkRegistrationInfo.Domain int domain, int videocallState) {
 
@@ -3150,6 +3269,12 @@
             releaseEmergencyCallDomainSelection(true, false);
             mEmergencyStateTracker.endCall(c);
         }
+        if (mNormalRoutingEmergencyConnection == c) {
+            Log.i(this, "onLocalHangup normal routing " + c.getTelecomCallId());
+            mNormalRoutingEmergencyConnection = null;
+            mEmergencyStateTracker.endNormalRoutingEmergencyCall(c);
+            mIsEmergencyCallPending = false;
+        }
     }
 
     @VisibleForTesting
@@ -3168,6 +3293,22 @@
     }
 
     @VisibleForTesting
+    public TelephonyConnection getNormalRoutingEmergencyConnection() {
+        return mNormalRoutingEmergencyConnection;
+    }
+
+    @VisibleForTesting
+    public void setNormalRoutingEmergencyConnection(TelephonyConnection c) {
+        mNormalRoutingEmergencyConnection = c;
+    }
+
+    @VisibleForTesting
+    public TelephonyConnection.TelephonyConnectionListener
+            getNormalRoutingEmergencyConnectionListener() {
+        return mNormalRoutingEmergencyConnectionListener;
+    }
+
+    @VisibleForTesting
     public TelephonyConnection.TelephonyConnectionListener
             getEmergencyConnectionSatelliteListener() {
         return mEmergencyConnectionSatelliteListener;
@@ -3556,9 +3697,20 @@
         }
 
         Call ringingCall = phone.getRingingCall();
-        if (ringingCall == null || !ringingCall.isRinging()) {
-            completeConsumer.accept(true);
-            return CompletableFuture.completedFuture(null);
+        if (ringingCall == null
+                || ringingCall.getState() == Call.State.IDLE
+                || ringingCall.getState() == Call.State.DISCONNECTED) {
+            // Check the ImsPhoneCall in DISCONNECTING state.
+            Phone imsPhone = phone.getImsPhone();
+            if (imsPhone != null) {
+                ringingCall = imsPhone.getRingingCall();
+            }
+            if (imsPhone == null || ringingCall == null
+                    || ringingCall.getState() == Call.State.IDLE
+                    || ringingCall.getState() == Call.State.DISCONNECTED) {
+                completeConsumer.accept(true);
+                return CompletableFuture.completedFuture(null);
+            }
         }
         Log.i(this, "checkAndRejectIncomingCall found a ringing call");
 
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index db47d67..e76c11c 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -2373,7 +2373,7 @@
         setupForDialForDomainSelection(mPhone0, selectedDomain, true);
 
         doReturn(mInternalConnection2).when(mCall).getLatestConnection();
-        doReturn(true).when(mCall).isRinging();
+        doReturn(Call.State.INCOMING).when(mCall).getState();
         doReturn(mCall).when(mPhone0).getRingingCall();
 
         mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
@@ -2435,7 +2435,7 @@
                 mPhone0, selectedDomain, preciseDisconnectCause, disconnectCause, true);
 
         doReturn(mInternalConnection2).when(mCall).getLatestConnection();
-        doReturn(true).when(mCall).isRinging();
+        doReturn(Call.State.DISCONNECTING).when(mCall).getState();
         doReturn(mCall).when(mPhone0).getRingingCall();
 
         assertTrue(mTestConnectionService.maybeReselectDomain(c, null, true,
@@ -2487,6 +2487,30 @@
                 createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
                         TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
 
+        ArgumentCaptor<TelephonyConnection> connectionCaptor =
+                ArgumentCaptor.forClass(TelephonyConnection.class);
+        ArgumentCaptor<Consumer<Boolean>> consumerCaptor = ArgumentCaptor
+                .forClass(Consumer.class);
+
+        verify(mEmergencyStateTracker).startNormalRoutingEmergencyCall(eq(mPhone0),
+                connectionCaptor.capture(), consumerCaptor.capture());
+
+        TelephonyConnection tc = connectionCaptor.getValue();
+
+        assertNotNull(tc);
+        assertNotNull(mTestConnectionService.getNormalRoutingEmergencyConnection());
+        assertEquals(mTestConnectionService.getNormalRoutingEmergencyConnection(), tc);
+
+        verify(mDomainSelectionResolver, never())
+                .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+        verify(mNormalCallDomainSelectionConnection, never()).createNormalConnection(any(), any());
+
+        Consumer<Boolean> consumer = consumerCaptor.getValue();
+
+        assertNotNull(consumer);
+
+        consumer.accept(true);
+
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());
@@ -2504,6 +2528,59 @@
     }
 
     @Test
+    public void testDomainSelectionNormalRoutingEmergencyNumberAndDiscarded() throws Exception {
+        setupForCallTest();
+        int selectedDomain = DOMAIN_PS;
+
+        EmergencyNumber emergencyNumber = new EmergencyNumber(TEST_EMERGENCY_NUMBER, "", "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                Collections.emptyList(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        setupForDialForDomainSelection(mPhone0, selectedDomain, false);
+        doReturn(true).when(mTelephonyManagerProxy).isCurrentEmergencyNumber(anyString());
+        doReturn(emergencyNumber).when(mEmergencyNumberTracker).getEmergencyNumber(anyString());
+        doReturn(Arrays.asList(emergencyNumber)).when(mEmergencyNumberTracker).getEmergencyNumbers(
+                anyString());
+
+        mTestConnectionService.onCreateOutgoingConnection(PHONE_ACCOUNT_HANDLE_1,
+                createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
+                        TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
+
+        ArgumentCaptor<TelephonyConnection> connectionCaptor =
+                ArgumentCaptor.forClass(TelephonyConnection.class);
+        ArgumentCaptor<Consumer<Boolean>> consumerCaptor = ArgumentCaptor
+                .forClass(Consumer.class);
+
+        verify(mEmergencyStateTracker).startNormalRoutingEmergencyCall(eq(mPhone0),
+                connectionCaptor.capture(), consumerCaptor.capture());
+
+        TelephonyConnection tc = connectionCaptor.getValue();
+
+        assertNotNull(tc);
+        assertNotNull(mTestConnectionService.getNormalRoutingEmergencyConnection());
+        assertEquals(mTestConnectionService.getNormalRoutingEmergencyConnection(), tc);
+
+        verify(mDomainSelectionResolver, never())
+                .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+        verify(mNormalCallDomainSelectionConnection, never()).createNormalConnection(any(), any());
+
+        Consumer<Boolean> consumer = consumerCaptor.getValue();
+
+        assertNotNull(consumer);
+
+        // Discard dialing
+        tc.hangup(android.telephony.DisconnectCause.LOCAL);
+
+        consumer.accept(true);
+
+        verify(mDomainSelectionResolver, never())
+                .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+        verify(mNormalCallDomainSelectionConnection, never()).createNormalConnection(any(), any());
+    }
+
+    @Test
     public void testDomainSelectionDialedSimEmergencyNumberOnlyFalse() throws Exception {
         setupForCallTest();
 
@@ -2578,6 +2655,30 @@
                 createConnectionRequest(PHONE_ACCOUNT_HANDLE_1,
                         TEST_EMERGENCY_NUMBER, TELECOM_CALL_ID1));
 
+        ArgumentCaptor<TelephonyConnection> connectionCaptor =
+                ArgumentCaptor.forClass(TelephonyConnection.class);
+        ArgumentCaptor<Consumer<Boolean>> consumerCaptor = ArgumentCaptor
+                .forClass(Consumer.class);
+
+        verify(mEmergencyStateTracker).startNormalRoutingEmergencyCall(eq(mPhone0),
+                connectionCaptor.capture(), consumerCaptor.capture());
+
+        TelephonyConnection tc = connectionCaptor.getValue();
+
+        assertNotNull(tc);
+        assertNotNull(mTestConnectionService.getNormalRoutingEmergencyConnection());
+        assertEquals(mTestConnectionService.getNormalRoutingEmergencyConnection(), tc);
+
+        verify(mDomainSelectionResolver, never())
+                .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
+        verify(mNormalCallDomainSelectionConnection, never()).createNormalConnection(any(), any());
+
+        Consumer<Boolean> consumer = consumerCaptor.getValue();
+
+        assertNotNull(consumer);
+
+        consumer.accept(true);
+
         verify(mDomainSelectionResolver)
                 .getDomainSelectionConnection(eq(mPhone0), eq(SELECTOR_TYPE_CALLING), eq(false));
         verify(mNormalCallDomainSelectionConnection).createNormalConnection(any(), any());