Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 54e7645a authored by Hall Liu's avatar Hall Liu
Browse files

Revise BT active device detection

When figuring out which BT device is currently routing audio, use the
getActiveDevice(s) APIs provided by BT and use the last-received
ACTIVE_DEVICE_CHANGED broadcast as a fallback in case multiple active
devices are detected across profiles

Bug: 122359801
Test: unit, manual
Change-Id: Id5ae17abcee023ddb9172968b9def7de38d23c63
parent 07a279af
Loading
Loading
Loading
Loading
+23 −11
Original line number Diff line number Diff line
@@ -450,6 +450,7 @@ public class BluetoothRouteManager extends StateMachine {
    // 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 @@ public class BluetoothRouteManager extends StateMachine {
        } else {
            mHfpActiveDeviceCache = device;
        }

        if (device != null) mMostRecentlyReportedActiveDevice = device;

        boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
                || mHfpActiveDeviceCache != null;

@@ -690,30 +694,38 @@ public class BluetoothRouteManager extends StateMachine {
        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;
    }

    /**
+1 −1
Original line number Diff line number Diff line
@@ -136,7 +136,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver {
        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) {
+45 −15
Original line number Diff line number Diff line
@@ -38,7 +38,9 @@ import org.junit.runners.JUnit4;
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 @@ public class BluetoothRouteManagerTest extends TelecomTestCase {
    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 class BluetoothRouteManagerTest extends TelecomTestCase {
    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);
@@ -90,12 +93,31 @@ public class BluetoothRouteManagerTest extends TelecomTestCase {
        sm.quitNow();
    }

    @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 @@ public class BluetoothRouteManagerTest extends TelecomTestCase {
        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
+8 −7
Original line number Diff line number Diff line
@@ -265,9 +265,8 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase {
                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 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase {
            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 @@ public class BluetoothRouteTransitionTests extends TelecomTestCase {
        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);
        }
    }