Merge "Revise BT active device detection" am: 46d60dd323 am: 95639b3fc2
am: d97d321413

Change-Id: Ia9acd7a8cc740abf1ca5b47b3f5910a3b681b2c1
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index e53488c..df7896c 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -450,6 +450,7 @@
     // Tracks the active devices in the BT stack (HFP or hearing aid).
     private BluetoothDevice mHfpActiveDeviceCache = null;
     private BluetoothDevice mHearingAidActiveDeviceCache = null;
+    private BluetoothDevice mMostRecentlyReportedActiveDevice = null;
 
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
             BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
@@ -588,6 +589,9 @@
         } else {
             mHfpActiveDeviceCache = device;
         }
+
+        if (device != null) mMostRecentlyReportedActiveDevice = device;
+
         boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
                 || mHfpActiveDeviceCache != null;
 
@@ -690,30 +694,38 @@
         BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
         BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getHearingAidService();
 
+        BluetoothDevice hfpActiveDevice = null;
+        BluetoothDevice hearingAidActiveDevice = null;
+
         if (bluetoothHeadset == null && bluetoothHearingAid == null) {
             Log.i(this, "getBluetoothAudioConnectedDevice: no service available.");
             return null;
         }
 
         if (bluetoothHeadset != null) {
-            for (BluetoothDevice device : bluetoothHeadset.getConnectedDevices()) {
-                boolean isAudioOn = bluetoothHeadset.getAudioState(device)
-                        != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-                Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
-                        + "for headset: " + device);
-                if (isAudioOn) {
-                    return device;
-                }
-            }
+            hfpActiveDevice = bluetoothHeadset.getActiveDevice();
         }
+
         if (bluetoothHearingAid != null) {
             for (BluetoothDevice device : bluetoothHearingAid.getActiveDevices()) {
                 if (device != null) {
-                    return device;
+                    hearingAidActiveDevice = device;
+                    break;
                 }
             }
         }
-        return null;
+
+        // Return the active device reported by either HFP or hearing aid. If both are reporting
+        // active devices, go with the most recent one as reported by the receiver.
+        if (hfpActiveDevice != null) {
+            if (hearingAidActiveDevice != null) {
+                Log.i(this, "Both HFP and hearing aid are reporting active devices. Going with"
+                        + " the most recently reported active device: %s");
+                return mMostRecentlyReportedActiveDevice;
+            }
+            return hfpActiveDevice;
+        }
+        return hearingAidActiveDevice;
     }
 
     /**
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 0176a43..da2b8f1 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -136,7 +136,7 @@
         boolean isHearingAid =
                 BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction());
         Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
-                isHearingAid ? "heading aid" : "HFP");
+                isHearingAid ? "hearing aid" : "HFP");
 
         mBluetoothRouteManager.onActiveDeviceChanged(device, isHearingAid);
         if (isHearingAid) {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 42626d9..060031d 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -38,7 +38,9 @@
 import org.mockito.Mock;
 
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
@@ -55,6 +57,7 @@
     static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
     static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
     static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
+    static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04");
 
     @Mock private BluetoothDeviceManager mDeviceManager;
     @Mock private BluetoothHeadsetProxy mHeadsetProxy;
@@ -73,7 +76,7 @@
     public void testConnectHfpRetryWhileNotConnected() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null);
+        setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
         when(mHeadsetProxy.connectAudio()).thenReturn(false);
@@ -92,10 +95,29 @@
 
     @SmallTest
     @Test
+    public void testAmbiguousActiveDevice() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
+        setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
+                new BluetoothDevice[]{HEARING_AID_DEVICE}, DEVICE1, HEARING_AID_DEVICE);
+        sm.onActiveDeviceChanged(DEVICE1, false);
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE, true);
+        executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
+
+        verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
+        verifyConnectionAttempt(DEVICE1, 0);
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+                        + ":" + HEARING_AID_DEVICE.getAddress(),
+                sm.getCurrentState().getName());
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
     public void testConnectHfpRetryWhileConnectedToAnotherDevice() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
-        setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null);
+        setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
         when(mHeadsetProxy.connectAudio()).thenReturn(false);
@@ -127,18 +149,26 @@
         return sm;
     }
 
-    private void setupConnectedDevices(BluetoothDevice[] devices, BluetoothDevice activeDevice) {
-        when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
-        when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
-        when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
-        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
-                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
-        when(mBluetoothHearingAid.getConnectedDevices()).thenReturn(Collections.emptyList());
-        when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(null, null));
-        if (activeDevice != null) {
-            when(mHeadsetProxy.getAudioState(eq(activeDevice)))
-                    .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
-        }
+    private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
+            BluetoothDevice[] hearingAidDevices,
+            BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice) {
+        if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
+        if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
+
+        when(mDeviceManager.getNumConnectedDevices()).thenReturn(
+                hfpDevices.length + hearingAidDevices.length);
+        List<BluetoothDevice> allDevices = Stream.concat(
+                Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices))
+                .collect(Collectors.toList());
+
+        when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices);
+        when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices));
+        when(mHeadsetProxy.getActiveDevice()).thenReturn(hfpActiveDevice);
+
+        when(mBluetoothHearingAid.getConnectedDevices())
+                .thenReturn(Arrays.asList(hearingAidDevices));
+        when(mBluetoothHearingAid.getActiveDevices())
+                .thenReturn(Arrays.asList(hearingAidActiveDevice, null));
     }
 
     static void executeRoutingAction(BluetoothRouteManager brm, int message, String
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index f87da3c..2f68ac2 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -265,9 +265,8 @@
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = Log.createSubsession();
                 args.arg2 = mParams.initialDevice.getAddress();
+                when(mHeadsetProxy.getActiveDevice()).thenReturn(null);
                 sm.sendMessage(BluetoothRouteManager.BT_AUDIO_LOST, args);
-                when(mHeadsetProxy.getAudioState(eq(mParams.initialDevice)))
-                        .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                 return true;
             }).when(mDeviceManager).disconnectAudio();
         }
@@ -278,9 +277,14 @@
             sm.onActiveDeviceChanged(mParams.messageDevice,
                     mParams.hearingAidBtDevices.contains(mParams.messageDevice));
         } else if (mParams.messageType == BluetoothRouteManager.LOST_DEVICE) {
-            sm.onDeviceLost(mParams.messageDevice.getAddress());
             sm.onActiveDeviceChanged(null,
                     mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+            if (mParams.hearingAidBtDevices.contains(mParams.messageDevice)) {
+                when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(null, null));
+            } else {
+                when(mHeadsetProxy.getActiveDevice()).thenReturn(null);
+            }
+            sm.onDeviceLost(mParams.messageDevice.getAddress());
         } else {
             executeRoutingAction(sm, mParams.messageType,
                     mParams.messageDevice == null ? null : mParams.messageDevice.getAddress());
@@ -335,11 +339,8 @@
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
-        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
-                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
         if (audioOnDevice != null) {
-            when(mHeadsetProxy.getAudioState(eq(audioOnDevice)))
-                    .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
+            when(mHeadsetProxy.getActiveDevice()).thenReturn(audioOnDevice);
         }
     }