Bluetooth: Enable in-band ringing in vibration mode (4/4)

* Add RINGER_MODE_CHANGE message in CallAudioModeStateMachine to re-try
  startRinging() in RingingFocus state
* When HFP is connected, CallAudioRouteStateMachine will notify
  CallAudioModeStateMachine about new established SCO connection.
  CallAudioModeStateMachine will try to start playing ringtone at such
  event in case ringtone volume condition changes
* Added and adjusted unit tests in CallAudioRouteStateMachineTest,
  CallAudioModeStateMachineTest and CallAudioRouteTransitionTests

Bug: 72647074
Test: Call phone in vibration mode and hear ringtone on HFP enabled
      headset, verify that ringtone is only played through headset.
      Then disconnect headset and call again to verify that ringtone
      does not play through phone speaker in vibration mode.
      Telecom unit tests: lite_test_telecom

Change-Id: Ie00059213292005c3e3af0c771c148ec0dc22498
(cherry picked from commit fff818b3cd1f8ba91a5037bcb91b5478a5663a25)
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index ad446ec..7bc2519 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -93,6 +93,7 @@
 
         mPlayerFactory.setCallAudioManager(this);
         mCallAudioModeStateMachine.setCallAudioManager(this);
+        mCallAudioRouteStateMachine.setCallAudioManager(this);
     }
 
     @Override
@@ -385,6 +386,11 @@
                 CallAudioRouteStateMachine.TOGGLE_MUTE);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onRingerModeChange() {
+        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+    }
+
     @VisibleForTesting
     public void mute(boolean shouldMute) {
         Log.v(this, "mute, shouldMute: %b", shouldMute);
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index b5c7e7a..716f23a 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -87,6 +87,8 @@
 
     public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
 
+    public static final int RINGER_MODE_CHANGE = 5001;
+
     public static final int RUN_RUNNABLE = 9001;
 
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
@@ -105,6 +107,7 @@
         put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
         put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
         put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
+        put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
 
         put(RUN_RUNNABLE, "RUN_RUNNABLE");
     }};
@@ -202,18 +205,22 @@
     }
 
     private class RingingFocusState extends BaseState {
-        @Override
-        public void enter() {
-            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+        private void tryStartRinging() {
             if (mCallAudioManager.startRinging()) {
                 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                 mAudioManager.setMode(AudioManager.MODE_RINGTONE);
-                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
+                mCallAudioManager.setCallAudioRouteFocusState(
+                        CallAudioRouteStateMachine.RINGING_FOCUS);
             } else {
-                Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");
+                Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
             }
+        }
 
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+            tryStartRinging();
             mCallAudioManager.stopCallWaiting();
         }
 
@@ -275,6 +282,11 @@
                     transitionTo(args.foregroundCallIsVoip
                             ? mVoipCallFocusState : mSimCallFocusState);
                     return HANDLED;
+                case RINGER_MODE_CHANGE: {
+                    Log.i(LOG_TAG, "RINGING state, received RINGER_MODE_CHANGE");
+                    tryStartRinging();
+                    return HANDLED;
+                }
                 default:
                     // The forced focus switch commands are handled by BaseState.
                     return NOT_HANDLED;
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index facbf39..dfa6042 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -745,6 +745,10 @@
                     mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
             updateInternalCallAudioState();
+            // Do not send RINGER_MODE_CHANGE if no Bluetooth SCO audio device is available
+            if (mBluetoothRouteManager.getBluetoothAudioConnectedDevice() != null) {
+                mCallAudioManager.onRingerModeChange();
+            }
         }
 
         @Override
@@ -770,7 +774,9 @@
                     }
                     return HANDLED;
                 case BT_AUDIO_CONNECTED:
-                    // Nothing to do
+                    // Send ringer mode change because we transit to ActiveBluetoothState even
+                    // when HFP is connecting
+                    mCallAudioManager.onRingerModeChange();
                     return HANDLED;
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
@@ -1276,6 +1282,8 @@
     private CallAudioState mCurrentCallAudioState;
     private CallAudioState mLastKnownCallAudioState;
 
+    private CallAudioManager mCallAudioManager;
+
     public CallAudioRouteStateMachine(
             Context context,
             CallsManager callsManager,
@@ -1332,6 +1340,10 @@
         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
     }
 
+    public void setCallAudioManager(CallAudioManager callAudioManager) {
+        mCallAudioManager = callAudioManager;
+    }
+
     /**
      * Initializes the state machine with info on initial audio route, supported audio routes,
      * and mute status.
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 64f5fb8..f253d19 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -21,6 +21,7 @@
 
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -80,6 +81,51 @@
         verify(mCallAudioManager).stopCallWaiting();
     }
 
+    @SmallTest
+    @Test
+    public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ));
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(mAudioManager, never()).setMode(anyInt());
+
+        verify(mCallAudioManager, never()).stopRinging();
+
+        verify(mCallAudioManager).stopCallWaiting();
+
+        when(mCallAudioManager.startRinging()).thenReturn(true);
+
+        sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        verify(mCallAudioManager).startRinging();
+        verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+        verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
+        verify(mCallAudioManager).setCallAudioRouteFocusState(
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+    }
+
+
     private void resetMocks() {
         reset(mCallAudioManager, mAudioManager);
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 76048b7..d4c3d1d 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -82,6 +82,7 @@
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
+    @Mock CallAudioManager mockCallAudioManager;
 
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
@@ -187,6 +188,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -229,6 +231,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -301,6 +304,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -335,6 +339,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(false);
@@ -368,6 +373,12 @@
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
+
+        when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                .thenReturn(bluetoothDevice1);
+        stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        verify(mockCallAudioManager, times(1)).onRingerModeChange();
     }
 
     @SmallTest
@@ -381,6 +392,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
 
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 345312e..609a488 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -146,6 +146,7 @@
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
+    @Mock CallAudioManager mockCallAudioManager;
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
@@ -218,6 +219,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
 
@@ -303,6 +305,7 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(