Merge "Add ringing state for bluetooth audio route" into nyc-mr1-dev
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 92adca6..c4dbab1 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -213,7 +213,7 @@
 
             mCallAudioManager.stopCallWaiting();
             mCallAudioManager.startRinging();
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
         }
 
         @Override
@@ -288,7 +288,7 @@
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
             mMostRecentMode = AudioManager.MODE_IN_CALL;
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
@@ -350,7 +350,7 @@
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
@@ -407,7 +407,7 @@
             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(mMostRecentMode);
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
         @Override
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f4bdc4d..a501dca 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -105,7 +105,8 @@
 
     /** Valid values for mAudioFocusType */
     public static final int NO_FOCUS = 1;
-    public static final int HAS_FOCUS = 2;
+    public static final int ACTIVE_FOCUS = 2;
+    public static final int RINGING_FOCUS = 3;
 
     private static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{
         put(CallAudioState.ROUTE_BLUETOOTH, Log.Events.AUDIO_ROUTE_BT);
@@ -135,6 +136,8 @@
         put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
         put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
 
+        put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
+
         put(MUTE_ON, "MUTE_ON");
         put(MUTE_OFF, "MUTE_OFF");
         put(TOGGLE_MUTE, "TOGGLE_MUTE");
@@ -148,6 +151,7 @@
     private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
     private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
     private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
+    private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute";
     private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
     private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
     private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
@@ -160,7 +164,7 @@
         if (msg.obj != null && msg.obj instanceof Session) {
             String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
             Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
-            Log.i(this, "Message received: %s=%d", messageCodeName, msg.what);
+            Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
         }
     }
 
@@ -217,6 +221,9 @@
                 case USER_SWITCH_BASELINE_ROUTE:
                     sendInternalMessage(calculateBaselineRouteMessage(true));
                     return HANDLED;
+                case SWITCH_FOCUS:
+                    mAudioFocusType = msg.arg1;
+                    return NOT_HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -268,7 +275,8 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -350,7 +358,7 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveEarpieceRoute);
                     }
                     return HANDLED;
@@ -449,7 +457,8 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -527,7 +536,7 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveHeadsetRoute);
                     }
                     return HANDLED;
@@ -652,6 +661,8 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         reinitialize();
+                    } else if (msg.arg1 == RINGING_FOCUS) {
+                        transitionTo(mRingingBluetoothRoute);
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECT:
@@ -663,6 +674,87 @@
         }
     }
 
+    class RingingBluetoothRoute extends BluetoothRoute {
+        @Override
+        public String getName() {
+            return RINGING_BLUETOOTH_ROUTE_NAME;
+        }
+
+        @Override
+        public boolean isActive() {
+            return false;
+        }
+
+        @Override
+        public void enter() {
+            super.enter();
+            setSpeakerphoneOn(false);
+            // Do not enable SCO audio here, since RING is being sent to the headset.
+            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
+                    mAvailableRoutes);
+            setSystemAudioState(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(mCurrentCallAudioState);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            switch (msg.what) {
+                case USER_SWITCH_EARPIECE:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_EARPIECE:
+                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        transitionTo(mActiveEarpieceRoute);
+                    } else {
+                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
+                    }
+                    return HANDLED;
+                case SWITCH_BLUETOOTH:
+                case USER_SWITCH_BLUETOOTH:
+                    // Nothing to do
+                    return HANDLED;
+                case USER_SWITCH_HEADSET:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_HEADSET:
+                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        transitionTo(mActiveHeadsetRoute);
+                    } else {
+                        Log.w(this, "Ignoring switch to headset command. Not available.");
+                    }
+                    return HANDLED;
+                case USER_SWITCH_SPEAKER:
+                    mHasUserExplicitlyLeftBluetooth = true;
+                    // fall through
+                case SWITCH_SPEAKER:
+                    transitionTo(mActiveSpeakerRoute);
+                    return HANDLED;
+                case SWITCH_FOCUS:
+                    if (msg.arg1 == NO_FOCUS) {
+                        reinitialize();
+                    } else if (msg.arg1 == ACTIVE_FOCUS) {
+                        transitionTo(mActiveBluetoothRoute);
+                    }
+                    return HANDLED;
+                case BT_AUDIO_DISCONNECT:
+                    // Ignore BT_AUDIO_DISCONNECT when ringing, since SCO audio should not be
+                    // connected.
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
     class QuiescentBluetoothRoute extends BluetoothRoute {
         @Override
         public String getName() {
@@ -717,8 +809,10 @@
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS) {
                         transitionTo(mActiveBluetoothRoute);
+                    } else if (msg.arg1 == RINGING_FOCUS) {
+                        transitionTo(mRingingBluetoothRoute);
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECT:
@@ -816,7 +910,8 @@
                     // fall through
                 case SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        transitionTo(mActiveBluetoothRoute);
+                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
+                                mActiveBluetoothRoute : mRingingBluetoothRoute);
                     } else {
                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
                     }
@@ -906,7 +1001,7 @@
                     // Nothing to do
                     return HANDLED;
                 case SWITCH_FOCUS:
-                    if (msg.arg1 == HAS_FOCUS) {
+                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveSpeakerRoute);
                     }
                     return HANDLED;
@@ -962,6 +1057,7 @@
     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
     private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
+    private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
     private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
@@ -972,6 +1068,7 @@
      * states
      */
     private int mAvailableRoutes;
+    private int mAudioFocusType;
     private boolean mWasOnSpeaker;
     private boolean mIsMuted;
 
@@ -1006,6 +1103,7 @@
         addState(mActiveHeadsetRoute);
         addState(mActiveBluetoothRoute);
         addState(mActiveSpeakerRoute);
+        addState(mRingingBluetoothRoute);
         addState(mQuiescentEarpieceRoute);
         addState(mQuiescentHeadsetRoute);
         addState(mQuiescentBluetoothRoute);
@@ -1025,6 +1123,7 @@
         mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
         mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
+        mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index c4526e4..615fd0e 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -50,6 +50,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -177,7 +178,7 @@
         stateMachine.initialize(initState);
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
         CallAudioState expectedMiddleState = new CallAudioState(false,
                 CallAudioState.ROUTE_WIRED_HEADSET,
@@ -209,7 +210,7 @@
         stateMachine.initialize(initState);
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE);
         CallAudioState expectedEndState = new CallAudioState(false,
@@ -230,6 +231,72 @@
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
+    @MediumTest
+    public void testBluetoothRinging() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+
+        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+    }
+
+    @MediumTest
+    public void testConnectBluetoothDuringRinging() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+
+        when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.RINGING_FOCUS);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        verify(mockBluetoothManager, never()).connectBluetoothAudio();
+        CallAudioState expectedEndState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        verifyNewSystemCallAudioState(initState, expectedEndState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+        verify(mockBluetoothManager, times(1)).connectBluetoothAudio();
+    }
+
     @SmallTest
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -677,7 +744,7 @@
         stateMachine.initialize(initState);
         // Make the state machine have focus so that we actually do something
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
-                CallAudioRouteStateMachine.HAS_FOCUS);
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(params.action);
 
         waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);