Merge "Import translations. DO NOT MERGE ANYWHERE" into main
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/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 68ef421..766db8e 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -602,7 +602,8 @@
             Log.w(this, "disconnectSco: Trying to disconnect audio but no headset service exists.");
         } else {
             result = mBluetoothHeadset.disconnectAudio();
-            Log.i(this, "disconnectSco: BluetoothHeadset#disconnectAudio()=%b", result);
+            Log.i(this, "disconnectSco: BluetoothHeadset#disconnectAudio()=%s",
+                    btCodeToString(result));
         }
         return result;
     }
@@ -888,8 +889,8 @@
             }
             if (getBluetoothHeadset() != null) {
                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
-                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%d",
-                        scoConnectionRequest);
+                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%s",
+                        btCodeToString(scoConnectionRequest));
                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
                         scoConnectionRequest
                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
@@ -944,8 +945,8 @@
             }
             if (getBluetoothHeadset() != null) {
                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
-                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%d",
-                        scoConnectionRequest);
+                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%s",
+                        btCodeToString(scoConnectionRequest));
                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
                         scoConnectionRequest
                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
@@ -1015,4 +1016,33 @@
     public void dump(IndentingPrintWriter pw) {
         mLocalLog.dump(pw);
     }
+
+    private String btCodeToString(int code) {
+        switch (code) {
+            case BluetoothStatusCodes.SUCCESS:
+                return "SUCCESS";
+            case BluetoothStatusCodes.ERROR_UNKNOWN:
+                return "ERROR_UNKNOWN";
+            case BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND:
+                return "ERROR_PROFILE_SERVICE_NOT_BOUND";
+            case BluetoothStatusCodes.ERROR_TIMEOUT:
+                return "ERROR_TIMEOUT";
+            case BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED:
+                return "ERROR_AUDIO_DEVICE_ALREADY_CONNECTED";
+            case BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES:
+                return "ERROR_NO_ACTIVE_DEVICES";
+            case BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE:
+                return "ERROR_NOT_ACTIVE_DEVICE";
+            case BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED:
+                return "ERROR_AUDIO_ROUTE_BLOCKED";
+            case BluetoothStatusCodes.ERROR_CALL_ACTIVE:
+                return "ERROR_CALL_ACTIVE";
+            case BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED:
+                return "ERROR_PROFILE_NOT_CONNECTED";
+            case BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED:
+                return "BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED";
+            default:
+                return Integer.toString(code);
+        }
+    }
 }
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;