DSDA: Reject outgoing call when ringing call present

Make sure we reject the outgoing call when there's a unanswered incoming
call. The user should see a message indicating that they cannot place a
call while the incoming call is unanswered.

Bug: 393957944
Test: atest CallSequencingTests
Test: Manual verification
Flag: com.android.server.telecom.flags.enable_call_sequencing
Change-Id: I0580c68a975f55678294174acc2faa65d6a54c10
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c950337..9d9f5e1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -423,6 +423,9 @@
     <!-- In-call screen: error message shown when the user attempts to place a call, but the live
          call cannot be held. -->
     <string name="callFailed_unholdable_call">Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call.</string>
+    <!-- In-call screen: error message shown when the user has attempted to place a new outgoing
+         call while there is already a call in ringing state. -->
+    <string name="callFailed_already_ringing">Cannot place a call as there is an unanswered incoming call. Answer or reject the incoming call prior to placing a new call.</string>
     <!-- In-call screen: error message shown when the user attempts to dial an MMI code, but there
          is an ongoing call on a different phone account. -->
     <string name="callFailed_reject_mmi">This MMI code is not available for calls across multiple accounts.</string>
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 39cd379..abcdd18 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -4461,6 +4461,11 @@
                 CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
     }
 
+    public boolean hasManagedRingingOrSimulatedRingingCall() {
+        return getFirstCallWithState(null /* callToSkip */, true /* skipSelfManaged */,
+                CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
+    }
+
     @VisibleForTesting
     public boolean onMediaButton(int type) {
         if (hasAnyCalls()) {
@@ -4604,11 +4609,11 @@
 
     @VisibleForTesting
     public Call getFirstCallWithState(int... states) {
-        return getFirstCallWithState(null, states);
+        return getFirstCallWithState(null, false /* skipSelfManaged */, states);
     }
 
     public Call getFirstCallWithLiveState() {
-        return getFirstCallWithState(null, LIVE_CALL_STATES);
+        return getFirstCallWithState(null, false /* skipSelfManaged */, LIVE_CALL_STATES);
     }
 
     @VisibleForTesting
@@ -4633,7 +4638,7 @@
      *
      * @param callToSkip Call that this method should skip while searching
      */
-    Call getFirstCallWithState(Call callToSkip, int... states) {
+    Call getFirstCallWithState(Call callToSkip, boolean skipSelfManaged, int... states) {
         for (int currentState : states) {
             // check the foreground first
             Call foregroundCall = getForegroundCall();
@@ -4655,6 +4660,10 @@
                     continue;
                 }
 
+                if (skipSelfManaged && call.isSelfManaged()) {
+                    continue;
+                }
+
                 if (currentState == call.getState()) {
                     return call;
                 }
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
index 5166c03..713d155 100644
--- a/src/com/android/server/telecom/callsequencing/CallSequencingController.java
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -697,6 +697,12 @@
      *         made for the outgoing call.
      */
     private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) {
+        // For the purely managed CS cases, check if there's a ringing call, in which case we will
+        // disallow the outgoing call.
+        if (!call.isSelfManaged() && mCallsManager.hasManagedRingingOrSimulatedRingingCall()) {
+            showErrorDialogForOutgoingDuringRingingCall(call);
+            return CompletableFuture.completedFuture(false);
+        }
         // Already room!
         if (!mCallsManager.hasMaximumLiveCalls(call)) {
             return CompletableFuture.completedFuture(true);
@@ -921,20 +927,34 @@
     }
 
     private void showErrorDialogForMaxOutgoingCall(Call call) {
-        call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
-        int stringId = R.string.callFailed_too_many_calls;
+        int resourceId = R.string.callFailed_too_many_calls;
         String reason = " there are two calls already in progress. Disconnect one of the calls "
                 + "or merge the calls.";
-        showErrorDialogForRestrictedOutgoingCall(mContext, stringId, TAG, reason);
+        showErrorDialogForFailedCall(call, CallFailureCause.MAX_OUTGOING_CALLS, resourceId, reason);
+    }
+
+    private void showErrorDialogForOutgoingDuringRingingCall(Call call) {
+        int resourceId = R.string.callFailed_already_ringing;
+        String reason = " can't place outgoing call with an unanswered incoming call.";
+        showErrorDialogForFailedCall(call, null, resourceId, reason);
     }
 
     private void showErrorDialogForCannotHoldCall(Call call, boolean setCallFailure) {
+        CallFailureCause cause = null;
         if (setCallFailure) {
-            call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
+            cause = CallFailureCause.CANNOT_HOLD_CALL;
         }
-        int stringId = R.string.callFailed_unholdable_call;
+        int resourceId = R.string.callFailed_unholdable_call;
         String reason = " unable to hold live call. Disconnect the unholdable call.";
-        showErrorDialogForRestrictedOutgoingCall(mContext, stringId, TAG, reason);
+        showErrorDialogForFailedCall(call, cause, resourceId, reason);
+    }
+
+    private void showErrorDialogForFailedCall(Call call, CallFailureCause cause, int resourceId,
+            String reason) {
+        if (cause != null) {
+            call.setStartFailCause(cause);
+        }
+        showErrorDialogForRestrictedOutgoingCall(mContext, resourceId, TAG, reason);
     }
 
     public Handler getHandler() {
diff --git a/tests/src/com/android/server/telecom/tests/CallSequencingTests.java b/tests/src/com/android/server/telecom/tests/CallSequencingTests.java
index f1861cb..64d2d7d 100644
--- a/tests/src/com/android/server/telecom/tests/CallSequencingTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallSequencingTests.java
@@ -560,6 +560,16 @@
 
     @Test
     @SmallTest
+    public void testMakeRoomForOutgoingCallFail_RingingCall() {
+        when(mNewCall.isSelfManaged()).thenReturn(false);
+        when(mCallsManager.hasManagedRingingOrSimulatedRingingCall()).thenReturn(true);
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(false, mNewCall);
+        assertFalse(waitForFutureResult(future, true));
+    }
+
+    @Test
+    @SmallTest
     public void testDisconnectCallSuccess() {
         when(mActiveCall.disconnect()).thenReturn(CompletableFuture.completedFuture(true));
         int previousState = CallState.ACTIVE;