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

Commit fac08f46 authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

ActiveDeviceManager: Fix handling disconnect of streaming set member

This patch fixes unit tests which failes when flag is enabled.
Also, behind the flag we add additional change, which removes reduntant
call of removeActiveDevice() when LeAudio device is disconnected.

Bug: 367213572
Bug: 374320313
Test: atest ActiveDeviceManagerTest
Flag: com.android.bluetooth.flags.adm_fix_disconnect_of_set_member
Change-Id: Iedccc3b6f6ed39af2c3b1940d7a6c4f02669ff72
parent 9bf35901
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSinkAudioPolicy;
import android.media.AudioDeviceCallback;
@@ -569,8 +570,8 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
            boolean hasFallbackDevice = false;
            if (Objects.equals(mLeAudioActiveDevice, device)) {
                hasFallbackDevice = setFallbackDeviceActiveLocked(device);
                if (!hasFallbackDevice) {
                    setLeAudioActiveDevice(null, false);
                if (!hasFallbackDevice && !Flags.admFixDisconnectOfSetMember()) {
                    leAudioService.removeActiveDevice(false);
                }
            }
            leAudioService.deviceDisconnected(device, hasFallbackDevice);
@@ -1095,13 +1096,25 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
            return false;
        }

        if (firstDevice == null || secondDevice == null) {
            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);
        int groupIdFirst = leAudioService.getGroupId(firstDevice);
        int groupIdSecond = leAudioService.getGroupId(secondDevice);

        if (groupIdFirst == BluetoothLeAudio.GROUP_ID_INVALID
                || groupIdSecond == BluetoothLeAudio.GROUP_ID_INVALID) {
            return false;
        }

        return groupIdFirst == groupIdSecond;
    }

    /**
@@ -1113,7 +1126,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
     */
    @GuardedBy("mLock")
    private boolean setFallbackDeviceActiveLocked(BluetoothDevice recentlyRemovedDevice) {
        Log.d(TAG, "setFallbackDeviceActive");
        Log.d(TAG, "setFallbackDeviceActive, recently removed: " + recentlyRemovedDevice);
        mDbManager = mAdapterService.getDatabase();
        List<BluetoothDevice> connectedHearingAidDevices = new ArrayList<>();
        if (!mHearingAidConnectedDevices.isEmpty()) {
+83 −41
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.media.AudioDeviceInfo;
import android.media.AudioDevicePort;
import android.media.AudioManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
@@ -385,10 +386,13 @@ public class ActiveDeviceManagerTest {
    @Test
    public void headsetRemoveActive_fallbackToLeAudio() {
        when(mHeadsetService.getFallbackDevice()).thenReturn(mHeadsetDevice);
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);

        InOrder order = inOrder(mLeAudioService);

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

        headsetConnected(mHeadsetDevice, false);
        mTestLooper.dispatchAll();
@@ -827,7 +831,28 @@ public class ActiveDeviceManagerTest {

    /** One LE Audio is connected and disconnected later. Should then set active device to null. */
    @Test
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void lastLeAudioDisconnected_clearLeAudioActive() {
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);

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

        leAudioDisconnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        verify(mLeAudioService).deviceDisconnected(mLeAudioDevice, false);
    }

    /** One LE Audio is connected and disconnected later. Should then set active device to null. */
    @Test
    @DisableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void lastLeAudioDisconnected_clearLeAudioActive_NoFixDisconnectFlag() {
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -857,11 +882,16 @@ public class ActiveDeviceManagerTest {
    }

    /**
     * Two LE Audio are connected and the current active is then disconnected. Should then set
     * active device to fallback device.
     * Two LE Audio Sets are connected and the current active Set is disconnected. The other
     * connected LeAudio Set shall become an active device.
     */
    @Test
    public void leAudioSecondDeviceDisconnected_fallbackDeviceActive() {
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);
        when(mLeAudioService.getGroupId(mLeAudioDevice2)).thenReturn(2);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice2);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -1101,6 +1131,7 @@ public class ActiveDeviceManagerTest {
    @Test
    public void leAudioAndA2dpConnectedThenA2dpDisconnected_fallbackToLeAudio() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
@@ -1143,13 +1174,13 @@ public class ActiveDeviceManagerTest {
    }

    /**
     * An LE Audio set connected. The active bud disconnected. Set active device returns false
     * indicating an issue (the other bud is also disconnected). Then the active device should be
     * removed and hasFallback should be set to false.
     * An LE Audio set connected. The active bud disconnected. Active device manager should not
     * choose other set member as active device.
     */
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mLeAudioService.getLeadDevice(any())).thenReturn(mLeAudioDevice);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
@@ -1157,71 +1188,76 @@ public class ActiveDeviceManagerTest {

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice2);

        Mockito.clearInvocations(mLeAudioService);

        // Return false to indicate an issue when setting new active device
        // (e.g. the other device disconnected as well).
        when(mLeAudioService.setActiveDevice(any())).thenReturn(false);
        verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice2);

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).removeActiveDevice(false);
        verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        verify(mLeAudioService).deviceDisconnected(mLeAudioDevice2, false);
    }

    /**
     * An LE Audio set connected. The active bud disconnected. Set active device returns true
     * indicating the other bud is going to be the active device. Then the active device should
     * change and hasFallback should be set to true.
     */
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_hasFallback() {
    @EnableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void leAudioSetConnectedGroupThenDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);
        when(mLeAudioService.getGroupId(mLeAudioDevice2)).thenReturn(1);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);

        InOrder order = inOrder(mLeAudioService);

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

        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice2);

        Mockito.clearInvocations(mLeAudioService);
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
        verify(mLeAudioService).deviceDisconnected(mLeAudioDevice2, true);
        order.verify(mLeAudioService, never()).setActiveDevice(any());
        order.verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        order.verify(mLeAudioService).deviceDisconnected(mLeAudioDevice2, false);

        leAudioDisconnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        order.verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        order.verify(mLeAudioService).deviceDisconnected(mLeAudioDevice, false);
    }

    @Test
    public void leAudioSetConnectedGroupThenDisconnected_noFallback() {
    @DisableFlags(Flags.FLAG_ADM_FIX_DISCONNECT_OF_SET_MEMBER)
    public void leAudioSetConnectedGroupThenDisconnected_noFallback_NoFixDisconnectFlag() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(1);
        when(mLeAudioService.getGroupId(mLeAudioDevice2)).thenReturn(1);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);
        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);

        InOrder order = inOrder(mLeAudioService);

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

        Mockito.clearInvocations(mLeAudioService);
        order.verify(mLeAudioService).setActiveDevice(mLeAudioDevice);

        when(mLeAudioService.getLeadDevice(mLeAudioDevice2)).thenReturn(mLeAudioDevice);
        leAudioConnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService, never()).setActiveDevice(any());

        Mockito.clearInvocations(mLeAudioService);
        order.verify(mLeAudioService, never()).setActiveDevice(any());

        leAudioDisconnected(mLeAudioDevice2);
        mTestLooper.dispatchAll();
        verify(mLeAudioService, never()).setActiveDevice(any());
        verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        order.verify(mLeAudioService, never()).setActiveDevice(any());
        order.verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());
        order.verify(mLeAudioService).deviceDisconnected(mLeAudioDevice2, false);

        leAudioDisconnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).removeActiveDevice(false);
        verify(mLeAudioService).deviceDisconnected(mLeAudioDevice2, false);
        order.verify(mLeAudioService).removeActiveDevice(false);
        order.verify(mLeAudioService).deviceDisconnected(mLeAudioDevice, false);
    }

    /**
@@ -1255,6 +1291,11 @@ public class ActiveDeviceManagerTest {
    @Test
    @EnableFlags(Flags.FLAG_ADM_VERIFY_ACTIVE_FALLBACK_DEVICE)
    public void sameDeviceAsAshaAndLeAudio_noFallbackOnSwitch() {
        /* Dual mode ASHA/LeAudio device from group 1 */
        when(mLeAudioService.getGroupId(mHearingAidDevice)).thenReturn(1);
        /* Different LeAudio only device from group 2 */
        when(mLeAudioService.getGroupId(mLeAudioDevice)).thenReturn(2);

        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        /* Connect first device as ASHA */
@@ -1262,11 +1303,12 @@ public class ActiveDeviceManagerTest {
        mTestLooper.dispatchAll();
        verify(mHearingAidService).setActiveDevice(mHearingAidDevice);

        /* Connect first device as LE Audio */
        leAudioConnected(mHearingAidDevice);
        /* Disconnect ASHA and connect first device as LE Audio */
        hearingAidDisconnected(mHearingAidDevice);
        mTestLooper.dispatchAll();
        verify(mHearingAidService).removeActiveDevice(false);
        verify(mHearingAidService).removeActiveDevice(true /* stop audio */);
        leAudioConnected(mHearingAidDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mHearingAidDevice);

        /* Connect second device as LE Audio. First device is disconnected with fallback to