Modify BT behavior when connecting to active device

When we try to connect to a device that's already recognized as active
by the BT stack, don't just ignore it -- do some resolution logic to
align everything with what's actually going on in the system.

This change is needed because we may end up in a weird situation with
hearing aids, where BluetoothRouteManager will report AudioOff while the
hearing aid device is still active. This currently happens because we
don't treat active hearing aids as audio-connected outside a call, and
it's not getting updated when a call starts.

This change doesn't fix the root of the problem, but we're planning on
changing all the bluetooth logic in the next release anyway, so this
patch will be sufficient for now.

Test: manual
Test: atest TelecomUnitTests
Fixes: 152724391
Change-Id: Id49dea147d766f45efd5ef2e719f2814b2769ac6
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 82d245d..461c68a 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1602,10 +1602,13 @@
             BluetoothDevice connectedDevice =
                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
             if (address == null && connectedDevice != null) {
-                // null means connect to any device, so don't bother reconnecting. Also, send a
-                // message to ourselves telling us that BT audio is already connected.
-                Log.i(this, "HFP audio already on. Skipping connecting.");
+                // null means connect to any device, so if we're already connected to some device,
+                // that means we can just tell ourselves that it's connected.
+                // Do still try to connect audio though, so that BluetoothRouteManager knows that
+                // there's an active call.
+                Log.i(this, "Bluetooth audio already on.");
                 sendInternalMessage(BT_AUDIO_CONNECTED);
+                mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
                 return;
             }
             if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index fe34be3..7211990 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -645,6 +645,16 @@
             Log.i(this, "No device with address %s available. Using %s instead.",
                     address, actualAddress);
         }
+
+        BluetoothDevice alreadyConnectedDevice = getBluetoothAudioConnectedDevice();
+        if (alreadyConnectedDevice != null && alreadyConnectedDevice.getAddress().equals(
+                actualAddress)) {
+            Log.i(this, "trying to connect to already connected device -- skipping connection"
+                    + " and going into the actual connected state.");
+            transitionToActualState();
+            return null;
+        }
+
         if (!mDeviceManager.connectAudio(actualAddress)) {
             boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
             Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 8ba6b63..b36d74b 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -346,6 +346,8 @@
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
+        when(mHeadsetProxy.getAudioState(nullable(BluetoothDevice.class)))
+                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
         if (audioOnDevice != null) {
             when(mHeadsetProxy.getActiveDevice()).thenReturn(audioOnDevice);
             when(mHeadsetProxy.getAudioState(audioOnDevice))
@@ -634,6 +636,22 @@
                 .setExpectedBluetoothInteraction(NONE)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
+
+        result.add(new BluetoothRouteTestParametersBuilder()
+                .setName("connect BT to an already active device when in audio off.")
+                .setInitialBluetoothState(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+                .setAudioOnDevice(DEVICE2)
+                .setActiveDevice(DEVICE2)
+                .setConnectedDevices(DEVICE2, DEVICE3)
+                .setHearingAidBtDevices(Collections.singletonList(DEVICE2))
+                .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+                .setMessageDevice(DEVICE2)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+                        + ":" + DEVICE2)
+                .build());
+
         return result;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 5a739a5..24476f0 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -54,9 +54,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -486,9 +488,10 @@
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
-        // Make sure that we've successfully switched to the active BT route without actually
-        // calling connectAudio.
-        verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
+        // Make sure that we've successfully switched to the active BT route and that we've
+        // called connectAudio on the right device.
+        verify(mockBluetoothRouteManager, atLeastOnce())
+                .connectBluetoothAudio(eq(bluetoothDevice1.getAddress()));
         assertTrue(stateMachine.isInActiveState());
     }