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

Commit 80c7d7ae authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Automerger Merge Worker
Browse files

Merge "LeAudioService: Fix handling of lead device disconnection" into tm-dev am: f6745c0f

parents 71f004c7 f6745c0f
Loading
Loading
Loading
Loading
+26 −13
Original line number Diff line number Diff line
@@ -139,7 +139,7 @@ public class LeAudioService extends ProfileService {
            mIsActive = false;
            mActiveContexts = ACTIVE_CONTEXTS_NONE;
            mCodecStatus = null;
            mLostDevicesWhileStreaming = new ArrayList<>();
            mLostLeadDeviceWhileStreaming = null;
        }

        public Boolean mIsConnected;
@@ -147,7 +147,7 @@ public class LeAudioService extends ProfileService {
        public Integer mActiveContexts;
        public BluetoothLeAudioCodecStatus mCodecStatus;
        /* This can be non empty only for the streaming time */
        List<BluetoothDevice> mLostDevicesWhileStreaming;
        BluetoothDevice mLostLeadDeviceWhileStreaming;
    }

    List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>();
@@ -1066,18 +1066,19 @@ public class LeAudioService extends ProfileService {
    }

    private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) {
        for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) {
            LeAudioStateMachine sm = mStateMachines.get(dev);
            if (sm == null) {
                continue;
        if (DBG) {
            Log.d(TAG, " lost dev: " + descriptor.mLostLeadDeviceWhileStreaming);
        }

        LeAudioStateMachine sm = mStateMachines.get(descriptor.mLostLeadDeviceWhileStreaming);
        if (sm != null) {
            LeAudioStackEvent stackEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
            stackEvent.device = dev;
            stackEvent.device = descriptor.mLostLeadDeviceWhileStreaming;
            stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
            sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent);
        }
        descriptor.mLostLeadDeviceWhileStreaming = null;
    }

    // Suppressed since this is part of a local process
@@ -1110,14 +1111,17 @@ public class LeAudioService extends ProfileService {
                                        mActiveAudioOutDevice)
                                        || Objects.equals(device, mActiveAudioInDevice))
                                        && (getConnectedPeerDevices(groupId).size() > 1)) {
                                    descriptor.mLostDevicesWhileStreaming.add(device);

                                    if (DBG) Log.d(TAG, "Adding to lost devices : " + device);
                                    descriptor.mLostLeadDeviceWhileStreaming = device;
                                    return;
                                }
                                break;
                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
                                if (descriptor != null) {
                                    descriptor.mLostDevicesWhileStreaming.remove(device);
                                    if (DBG) Log.d(TAG, "Removing from lost devices : " + device);
                                    descriptor.mLostLeadDeviceWhileStreaming = null;
                                    /* Try to connect other devices from the group */
                                    connectSet(device);
                                }
@@ -1254,6 +1258,7 @@ public class LeAudioService extends ProfileService {
                                    ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
                            notifyGroupStatus = true;
                            /* Clear lost devices */
                            if (DBG) Log.d(TAG, "Clear for group: " + groupId);
                            clearLostDevicesWhileStreaming(descriptor);
                        }
                    } else {
@@ -1538,6 +1543,15 @@ public class LeAudioService extends ProfileService {
                return;
            }

            List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId);
            /* Let's check if the last connected device is really connected */
            if (connectedDevices.size() == 1
                    && Objects.equals(connectedDevices.get(0),
                            descriptor.mLostLeadDeviceWhileStreaming)) {
                clearLostDevicesWhileStreaming(descriptor);
                return;
            }

            if (getConnectedPeerDevices(myGroupId).isEmpty()){
                descriptor.mIsConnected = false;
                if (descriptor.mIsActive) {
@@ -2584,9 +2598,8 @@ public class LeAudioService extends ProfileService {
            ProfileService.println(sb, "    mActiveContexts: " + descriptor.mActiveContexts);
            ProfileService.println(sb, "    group lead: " + getConnectedGroupLeadDevice(groupId));
            ProfileService.println(sb, "    first device: " + getFirstDeviceFromGroup(groupId));
            for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) {
                ProfileService.println(sb, "        lost device: " + dev);
            }
            ProfileService.println(sb, "    lost lead device: "
                    + descriptor.mLostLeadDeviceWhileStreaming);
        }
    }
}
+161 −0
Original line number Diff line number Diff line
@@ -26,7 +26,11 @@ import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -42,6 +46,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.ParcelUuid;

import androidx.test.InstrumentationRegistry;
@@ -68,6 +73,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;

@@ -457,6 +463,23 @@ public class LeAudioServiceTest {
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    private void injectNoVerifyDeviceConnected(BluetoothDevice device) {
        generateUnexpectedConnectionMessageFromNative(device,
                LeAudioStackEvent.CONNECTION_STATE_CONNECTED,
                LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED);
    }

    private void injectAndVerifyDeviceDisconnected(BluetoothDevice device) {
        generateConnectionMessageFromNative(device,
                LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED,
                LeAudioStackEvent.CONNECTION_STATE_CONNECTED);
    }

    private void injectNoVerifyDeviceDisconnected(BluetoothDevice device) {
        generateUnexpectedConnectionMessageFromNative(device,
                LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED,
                LeAudioStackEvent.CONNECTION_STATE_CONNECTED);
    }
    /**
     * Test that the outgoing connect/disconnect and audio switch is successful.
     */
@@ -1220,4 +1243,142 @@ public class LeAudioServiceTest {
        onGroupCodecConfChangedCallbackCalled = false;
        mService.mLeAudioCallbacks.unregister(leAudioCallbacks);
    }

    private void verifyActiveDeviceStateIntent(int timeoutMs, BluetoothDevice device) {
        Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device));
        assertThat(intent).isNotNull();
        assertThat(intent.getAction())
                .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
        assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
    }

    /**
     * Test native interface group status message handling
     */
    @Test
    public void testLeadGroupDeviceDisconnects() {
        int groupId = 1;
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5;
        int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        BluetoothDevice leadDevice;
        BluetoothDevice memberDevice = mLeftDevice;

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mLeftDevice, groupId);
        connectTestDevice(mRightDevice, groupId);

        leadDevice = mService.getConnectedGroupLeadDevice(groupId);
        if (Objects.equals(leadDevice, mLeftDevice)) {
                memberDevice = mRightDevice;
        }

        assertThat(mService.setActiveDevice(leadDevice)).isTrue();

        //Add location support
        LeAudioStackEvent audioConfChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED);
        audioConfChangedEvent.valueInt1 = direction;
        audioConfChangedEvent.valueInt2 = groupId;
        audioConfChangedEvent.valueInt3 = snkAudioLocation;
        audioConfChangedEvent.valueInt4 = srcAudioLocation;
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        //Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        groupStatusChangedEvent.valueInt1 = groupId;
        groupStatusChangedEvent.valueInt2 = groupStatus;
        mService.messageFromNative(groupStatusChangedEvent);

        assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue();
        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(),
                        any(BluetoothProfileConnectionInfo.class));

        verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice);
        injectNoVerifyDeviceDisconnected(leadDevice);

        // We should not change the audio device
        assertThat(mService.getConnectionState(leadDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);

        injectAndVerifyDeviceDisconnected(memberDevice);

        // Verify the connection state broadcast, and that we are in Connecting state
        verifyConnectionStateIntent(TIMEOUT_MS, leadDevice, BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_CONNECTED);

        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(leadDevice),
                any(BluetoothProfileConnectionInfo.class));

    }

    /**
     * Test native interface group status message handling
     */
    @Test
    public void testLeadGroupDeviceReconnects() {
        int groupId = 1;
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5;
        int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        BluetoothDevice leadDevice;
        BluetoothDevice memberDevice = mLeftDevice;

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mLeftDevice, groupId);
        connectTestDevice(mRightDevice, groupId);

        leadDevice = mService.getConnectedGroupLeadDevice(groupId);
        if (Objects.equals(leadDevice, mLeftDevice)) {
                memberDevice = mRightDevice;
        }

        assertThat(mService.setActiveDevice(leadDevice)).isTrue();

        //Add location support
        LeAudioStackEvent audioConfChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED);
        audioConfChangedEvent.valueInt1 = direction;
        audioConfChangedEvent.valueInt2 = groupId;
        audioConfChangedEvent.valueInt3 = snkAudioLocation;
        audioConfChangedEvent.valueInt4 = srcAudioLocation;
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        //Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        groupStatusChangedEvent.valueInt1 = groupId;
        groupStatusChangedEvent.valueInt2 = groupStatus;
        mService.messageFromNative(groupStatusChangedEvent);

        assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue();
        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(),
                        any(BluetoothProfileConnectionInfo.class));

        verifyActiveDeviceStateIntent(TIMEOUT_MS, leadDevice);
        /* We don't want to distribute DISCONNECTION event, instead will try to reconnect
         * (in native)
         */
        injectNoVerifyDeviceDisconnected(leadDevice);
        assertThat(mService.getConnectionState(leadDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);

        /* Reconnect device, there should be no intent about that, as device was pretending
         * connected
         */
        injectNoVerifyDeviceConnected(leadDevice);

        injectAndVerifyDeviceDisconnected(memberDevice);
        injectAndVerifyDeviceDisconnected(leadDevice);

        verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice),
                any(BluetoothProfileConnectionInfo.class));
    }
}