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

Commit 7f23913c authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Łukasz Rymanowski (xWF)
Browse files

ActiveDeviceManager: Fix handling disconnect of a streaming set member

This patch fixes issue when after disconnecting LeAudio device while
stream, music was transfered to the Speaker.

ActiveDeviceManager, while handling disconnection of the first set
member, incorrectly assumed that another set member is a fallback
device. Then, ActiveDeviceManager instructed LeAudioService to remove
itself from being Active device, but also set flag `hasFallbackDevice`
which later cause the problem

Bug: 367213572
Bug: 374320313
Test: atest ActiveDeviceManagerTest
Flag: com.android.bluetooth.flags.adm_fix_disconnect_of_set_member

Change-Id: I6ea853a2565f9ce56ef8eaa6df8d4d515c065a8d
parent 729bf28b
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -1041,6 +1041,23 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        return false;
    }

    @GuardedBy("mLock")
    private boolean areSameGroupMembers(BluetoothDevice firstDevice, BluetoothDevice secondDevice) {

        if (!Flags.admFixDisconnectOfSetMember()) {
            /* This function shall return false without the fix flag. */
            return false;
        }

        final LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService == null) {
            Log.e(TAG, "LeAudioService not available");
            return false;
        }

        return leAudioService.getGroupId(firstDevice) == leAudioService.getGroupId(secondDevice);
    }

    /**
     * TODO: This method can return true when a fallback device for an unrelated profile is found.
     * Take disconnected profile as an argument, and find the exact fallback device. Also, split
@@ -1083,6 +1100,13 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                    setLeAudioActiveDevice(null, hasFallbackDevice);
                } else {
                    Log.d(TAG, "Found a LE hearing aid fallback device: " + device);
                    if (areSameGroupMembers(recentlyRemovedDevice, device)) {
                        Log.d(
                                TAG,
                                "Do nothing, removed device belong to the same group as the"
                                        + " fallback device.");
                        return true;
                    }
                    setLeHearingAidActiveDevice(device);
                    setHearingAidActiveDevice(null, hasFallbackDevice);
                    setA2dpActiveDevice(null, hasFallbackDevice);
@@ -1152,6 +1176,14 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                setHearingAidActiveDevice(null, true);
            } else {
                Log.d(TAG, "Found a LE audio fallback device: " + device);
                if (areSameGroupMembers(recentlyRemovedDevice, device)) {
                    Log.d(
                            TAG,
                            "Do nothing, removed device belong to the same group as the fallback"
                                    + " device.");
                    return true;
                }

                if (!setLeAudioActiveDevice(device)) {
                    return false;
                }
@@ -1181,6 +1213,14 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                setHearingAidActiveDevice(null, true);
            } else {
                Log.d(TAG, "Found a LE audio fallback device: " + device);
                if (areSameGroupMembers(recentlyRemovedDevice, device)) {
                    Log.d(
                            TAG,
                            "Do nothing, removed device belong to the same group as the fallback"
                                    + " device.");
                    return true;
                }

                setLeAudioActiveDevice(device);
                if (!Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
+160 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -67,6 +68,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
@@ -87,6 +89,7 @@ public class ActiveDeviceManagerTest {
    private BluetoothDevice mHearingAidDevice;
    private BluetoothDevice mLeAudioDevice;
    private BluetoothDevice mLeAudioDevice2;
    private BluetoothDevice mLeAudioDevice3;
    private BluetoothDevice mLeHearingAidDevice;
    private BluetoothDevice mSecondaryAudioDevice;
    private BluetoothDevice mDualModeAudioDevice;
@@ -146,6 +149,7 @@ public class ActiveDeviceManagerTest {
        mSecondaryAudioDevice = TestUtils.getTestDevice(mAdapter, 6);
        mDualModeAudioDevice = TestUtils.getTestDevice(mAdapter, 7);
        mLeAudioDevice2 = TestUtils.getTestDevice(mAdapter, 8);
        mLeAudioDevice3 = TestUtils.getTestDevice(mAdapter, 9);
        mDeviceConnectionStack = new ArrayList<>();
        mMostRecentDevice = null;
        mOriginalDualModeAudioState = Utils.isDualModeAudioEnabled();
@@ -161,6 +165,7 @@ public class ActiveDeviceManagerTest {

        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice2);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice3)).thenReturn(mLeAudioDevice3);
        when(mLeAudioService.getLeadDevice(mDualModeAudioDevice)).thenReturn(mDualModeAudioDevice);
        when(mLeAudioService.getLeadDevice(mLeHearingAidDevice)).thenReturn(mLeHearingAidDevice);

@@ -839,6 +844,161 @@ public class ActiveDeviceManagerTest {
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
    }

    /**
     * One LE Audio set, containing two buds, is connected. When one device got disconnected
     * fallback device should not be set to true active device to fallback device.
     */
    @Test
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void leAudioSecondDeviceDisconnected_noFallbackDeviceActive_ModeNormal() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        InOrder order = inOrder(mLeAudioService);

        int groupId = 1;
        List<BluetoothDevice> groupDevices = List.of(mLeAudioDevice, mLeAudioDevice2);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        when(mLeAudioService.getGroupId(any())).thenReturn(groupId);
        when(mLeAudioService.getGroupDevices(groupId)).thenReturn(groupDevices);

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());
    }

    /**
     * One LE Audio set, containing two buds, is connected. When one device got disconnected
     * fallback device should not be set to true active device to fallback device.
     */
    @Test
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void leAudioSecondDeviceDisconnected_noFallbackDeviceActive_ModeInCall() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        InOrder order = inOrder(mLeAudioService);

        int groupId = 1;
        List<BluetoothDevice> groupDevices = List.of(mLeAudioDevice, mLeAudioDevice2);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        when(mLeAudioService.getGroupId(any())).thenReturn(groupId);
        when(mLeAudioService.getGroupDevices(groupId)).thenReturn(groupDevices);

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());
    }

    /**
     * One LE Audio set, containing two buds, is connected. When one device got disconnected
     * fallback device should not be set to true active device to fallback device.
     */
    @Test
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void twoLeAudioSets_OneSetDisconnected_FallbackToAnotherOne_ModeNormal() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        InOrder order = inOrder(mLeAudioService);

        int groupId = 1;
        List<BluetoothDevice> groupDevices = List.of(mLeAudioDevice, mLeAudioDevice2);

        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(groupId);
        when(mLeAudioService.getGroupId(mLeAudioDevice2)).thenReturn(groupId);
        when(mLeAudioService.getGroupDevices(groupId)).thenReturn(groupDevices);

        int groupId2 = 2;
        List<BluetoothDevice> groupDevicesId2 = List.of(mLeAudioDevice3);

        when(mLeAudioService.getGroupId(mLeAudioDevice3)).thenReturn(groupId2);
        when(mLeAudioService.getGroupDevices(groupId2)).thenReturn(groupDevicesId2);

        leAudioConnected(mLeAudioDevice3);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice3);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService).setActiveDevice(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice2);

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        // Should not encrease a number of this call.
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        leAudioDisconnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice3);
    }

    /**
     * One LE Audio set, containing two buds, is connected. When one device got disconnected
     * fallback device should not be set to true active device to fallback device.
     */
    @Test
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void twoLeAudioSets_OneSetDisconnected_FallbackToAnotherOne_ModeInCall() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        InOrder order = inOrder(mLeAudioService);

        int groupId = 1;
        List<BluetoothDevice> groupDevices = List.of(mLeAudioDevice, mLeAudioDevice2);

        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(groupId);
        when(mLeAudioService.getGroupId(mLeAudioDevice2)).thenReturn(groupId);
        when(mLeAudioService.getGroupDevices(groupId)).thenReturn(groupDevices);

        int groupId2 = 2;
        List<BluetoothDevice> groupDevicesId2 = List.of(mLeAudioDevice3);

        when(mLeAudioService.getGroupId(mLeAudioDevice3)).thenReturn(groupId2);
        when(mLeAudioService.getGroupDevices(groupId2)).thenReturn(groupDevicesId2);

        leAudioConnected(mLeAudioDevice3);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService).setActiveDevice(mLeAudioDevice3);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService).setActiveDevice(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        leAudioDisconnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, times(1)).setActiveDevice(mLeAudioDevice3);
    }

    /** A combo (A2DP + Headset) device is connected. Then an LE Audio is connected. */
    @Test
    public void leAudioActive_clearA2dpAndHeadsetActive() {