Fix a race condition in BT device handling

When a headset or hearing aid disconnects in favor of the other, we have
a few milliseconds between when BT sends a "device disconnected" for the
first device and when it tells us that the second device is connected.
This causes us to go into a non-BT audio route for a while, but this
sends a "please disconnect audio" message to BT, which disrupts the
setup on the second device.
This change removes the "please disconnect audio" after a BT-initiated
disconnect, since it's redundant anyway.

Test: manual, unit
Bug: 122920967
Change-Id: I5f60b4b68c66c68a73d1fc0dd4e3122b35706d5e
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 69245da..dcf1b27 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -154,7 +154,7 @@
     public static final int ACTIVE_FOCUS = 2;
     public static final int RINGING_FOCUS = 3;
 
-    /** Valid values for the argument for SWITCH_BASELINE_ROUTE */
+    /** Valid values for the first argument for SWITCH_BASELINE_ROUTE */
     public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0;
     public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
 
@@ -352,7 +352,6 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes, null,
                     mBluetoothRouteManager.getConnectedDevices());
@@ -547,7 +546,6 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -766,6 +764,32 @@
         }
 
         @Override
+        public void handleBtInitiatedDisconnect() {
+            // There's special-case state transitioning here -- if BT tells us that
+            // something got disconnected, we don't want to disconnect BT before
+            // transitioning, since BT might be trying to connect another device in the
+            // meantime.
+            int command = calculateBaselineRouteMessage(false, false);
+            switch (command) {
+                case SWITCH_EARPIECE:
+                    transitionTo(mActiveEarpieceRoute);
+                    break;
+                case SWITCH_HEADSET:
+                    transitionTo(mActiveHeadsetRoute);
+                    break;
+                case SWITCH_SPEAKER:
+                    transitionTo(mActiveSpeakerRoute);
+                    break;
+                default:
+                    Log.w(this, "Got unexpected code " + command + " when processing a"
+                            + " BT-initiated audio disconnect");
+                    // Some fallback logic to make sure we make it off the bluetooth route.
+                    super.handleBtInitiatedDisconnect();
+                    break;
+            }
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
             if (super.processMessage(msg) == HANDLED) {
                 return HANDLED;
@@ -776,6 +800,7 @@
                     // fall through
                 case SWITCH_EARPIECE:
                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        setBluetoothOff();
                         transitionTo(mActiveEarpieceRoute);
                     } else {
                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -799,6 +824,7 @@
                     // fall through
                 case SWITCH_HEADSET:
                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        setBluetoothOff();
                         transitionTo(mActiveHeadsetRoute);
                     } else {
                         Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -808,6 +834,7 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                    setBluetoothOff();
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -822,7 +849,7 @@
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECTED:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+                    handleBtInitiatedDisconnect();
                     return HANDLED;
                 default:
                     return NOT_HANDLED;
@@ -1000,6 +1027,10 @@
             return CallAudioState.ROUTE_BLUETOOTH;
         }
 
+        public void handleBtInitiatedDisconnect() {
+            sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+        }
+
         @Override
         public boolean processMessage(Message msg) {
             if (super.processMessage(msg) == HANDLED) {
@@ -1014,7 +1045,7 @@
                             + " have been null while we were in BT route.");
                     return HANDLED;
                 case BT_ACTIVE_DEVICE_GONE:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+                    handleBtInitiatedDisconnect();
                     mWasOnSpeaker = false;
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
@@ -1048,7 +1079,6 @@
             super.enter();
             mWasOnSpeaker = true;
             setSpeakerphoneOn(true);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 26d2419..e63fe9b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -507,7 +507,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
@@ -519,7 +519,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
@@ -711,7 +711,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
                 ON, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
@@ -723,7 +723,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl