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

Commit 1abf719d authored by Mariusz Skamra's avatar Mariusz Skamra Committed by Łukasz Rymanowski (xWF)
Browse files

le_audio: LeAudioService: Fix handling initial zero available contexts

This fixes handling initial zero contexts that may be reported once
device is paired. Then the contexts are back, the Phone would like to
activate the device. Thus we'd like to handle this device just like
the onces that the stack inactivate due to available contexts that
are gone.

Bug: 357472821
Bug: 367325041
Flag: com.android.bluetooth.flags.leaudio_unicast_no_available_contexts
Test: atest com.android.bluetooth.le_audio.LeAudioServiceTest
Change-Id: I62f913573dd6a8ba2c3bbbe3a578f7a2d4ecb0dc
parent dd74b362
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -249,7 +249,7 @@ public class LeAudioService extends ProfileService {
            mLostLeadDeviceWhileStreaming = null;
            mCurrentLeadDevice = null;
            mInbandRingtoneEnabled = isInbandRingtonEnabled;
            mAvailableContexts = 0;
            mAvailableContexts = Flags.leaudioUnicastNoAvailableContexts() ? null : 0;
            mInputSelectableConfig = new ArrayList<>();
            mOutputSelectableConfig = new ArrayList<>();
            mInactivatedDueToContextType = false;
@@ -2952,9 +2952,13 @@ public class LeAudioService extends ProfileService {
                return;
            }

            boolean ringtoneContextAvailable =
                    ((groupDescriptor.mAvailableContexts & BluetoothLeAudio.CONTEXT_TYPE_RINGTONE)
                            != 0);
            boolean ringtoneContextAvailable;
            if (groupDescriptor.mAvailableContexts != null) {
                ringtoneContextAvailable = ((groupDescriptor.mAvailableContexts &
                                            BluetoothLeAudio.CONTEXT_TYPE_RINGTONE) != 0);
            } else {
                ringtoneContextAvailable = false;
            }

            Log.d(
                    TAG,
@@ -3492,9 +3496,10 @@ public class LeAudioService extends ProfileService {
                                                    BluetoothLeAudio.GROUP_STATUS_INACTIVE));
                        }
                    }

                    boolean isInitial = descriptor.mAvailableContexts == null;
                    boolean availableContextChanged =
                            Integer.bitCount(descriptor.mAvailableContexts)
                                    != Integer.bitCount(available_contexts);
                            isInitial ? true : descriptor.mAvailableContexts != available_contexts;

                    descriptor.mDirection = direction;
                    descriptor.mAvailableContexts = available_contexts;
@@ -3519,6 +3524,13 @@ public class LeAudioService extends ProfileService {
                                            + " due to unavailable context types");
                            descriptor.mInactivatedDueToContextType = true;
                            setActiveGroupWithDevice(null, false);
                        } else if (isInitial) {
                            Log.i(
                                    TAG,
                                    " New group "
                                            + groupId
                                            + " with no context types available");
                            descriptor.mInactivatedDueToContextType = true;
                        }
                        return;
                    }
@@ -4144,7 +4156,7 @@ public class LeAudioService extends ProfileService {

            if (getConnectedPeerDevices(groupId).isEmpty()) {
                descriptor.mIsConnected = false;
                descriptor.mInactivatedDueToContextType = false;
                descriptor.mAvailableContexts = Flags.leaudioUnicastNoAvailableContexts() ? null : 0;
                if (descriptor.isActive()) {
                    /* Notify Native layer */
                    removeActiveDevice(hasFallbackDevice);
@@ -4495,7 +4507,7 @@ public class LeAudioService extends ProfileService {
                Log.e(TAG, "getGroupId: No valid descriptor for groupId: " + groupId);
                return false;
            }
            return descriptor.mAvailableContexts != 0;
            return descriptor.mAvailableContexts != null && descriptor.mAvailableContexts != 0;
        } finally {
            mGroupReadLock.unlock();
        }
+312 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.media.BluetoothProfileConnectionInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.sysprop.BluetoothProperties;

@@ -3109,6 +3110,317 @@ public class LeAudioServiceTest {
                        any(BluetoothProfileConnectionInfo.class));
    }

    /**
     * Test the group is activated once the available contexts are back.
     *
     * Scenario:
     *  1. Have a group of 2 devices that initially does not expose any available contexts.
     *     The group shall be inactive at this point.
     *  2. Once the available contexts are updated with non-zero value,
     *     the group shall become active.
     *  3. The available contexts are changed to zero. Group becomes inactive.
     *  4. The available contexts are back again. Group becomes active.
     */
    @Test
    @EnableFlags(Flags.FLAG_LEAUDIO_UNICAST_NO_AVAILABLE_CONTEXTS)
    public void testActivateGroupWhenAvailableContextAreBack_Scenario1() {
        int groupId = 1;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE;

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

        // Checks group device lists for groupId 1
        List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId);

        assertThat(groupDevicesById.size()).isEqualTo(2);
        assertThat(groupDevicesById.contains(mLeftDevice)).isTrue();
        assertThat(groupDevicesById.contains(mRightDevice)).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 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        assertThat(mService.setActiveDevice(mLeftDevice)).isFalse();
        verify(mNativeInterface, times(0)).groupSetActive(groupId);

        // Expect device to be active
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(groupId);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));

        reset(mAudioManager);
        reset(mNativeInterface);

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(-1);
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);

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

        reset(mNativeInterface);
        reset(mAudioManager);

        // Expect device to be active
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(groupId);
        reset(mNativeInterface);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));
    }

    /**
     * Test the group is activated once the available contexts are back.
     *
     * Scenario:
     *  1. Have a group of 2 devices. The available contexts are non-zero.
     *     The group shall be active at this point.
     *  2. Once the available contexts are updated with zero value,
     *     the group shall become inactive.
     *  3. All group devices are disconnected.
     *  4. Group devices are reconnected. The available contexts are still zero.
     *  4. The available contexts are updated with non-zero value. Group becomes active.
     */
    @Test
    @EnableFlags(Flags.FLAG_LEAUDIO_UNICAST_NO_AVAILABLE_CONTEXTS)
    public void testActivateDeviceWhenAvailableContextAreBack_Scenario2() {
        int groupId = 1;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE;

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

        // Checks group device lists for groupId 1
        List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId);

        assertThat(groupDevicesById.size()).isEqualTo(2);
        assertThat(groupDevicesById.contains(mLeftDevice)).isTrue();
        assertThat(groupDevicesById.contains(mRightDevice)).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);

        assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();
        verify(mNativeInterface, times(1)).groupSetActive(groupId);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));

        reset(mAudioManager);
        reset(mNativeInterface);

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(-1);
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);

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

        reset(mNativeInterface);
        reset(mAudioManager);

        // Send a message to trigger disconnection completed to the left device
        injectAndVerifyDeviceDisconnected(mLeftDevice);

        // Send a message to trigger disconnection completed to the right device
        injectAndVerifyDeviceDisconnected(mRightDevice);

        // Verify the list of connected devices
        assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isFalse();
        assertThat(mService.getConnectedDevices().contains(mRightDevice)).isFalse();

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        generateConnectionMessageFromNative(
                mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
        assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue();

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        generateConnectionMessageFromNative(
                mRightDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mRightDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
        assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue();

        // Expect device to be active
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(groupId);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));
    }

    /**
     * Test the group is activated once the available contexts are back.
     *
     * Scenario:
     *  1. Have a group of 2 devices. The available contexts are non-zero.
     *     The group shall be active at this point.
     *  2. All group devices are disconnected.
     *  3. Group devices are reconnected. The available contexts are zero.
     *  4. The available contexts are updated with non-zero value. Group becomes active.
     */
    @Test
    @EnableFlags(Flags.FLAG_LEAUDIO_UNICAST_NO_AVAILABLE_CONTEXTS)
    public void testActivateDeviceWhenAvailableContextAreBack_Scenario3() {
        int groupId = 1;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE;

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

        // Checks group device lists for groupId 1
        List<BluetoothDevice> groupDevicesById = mService.getGroupDevices(groupId);

        assertThat(groupDevicesById.size()).isEqualTo(2);
        assertThat(groupDevicesById.contains(mLeftDevice)).isTrue();
        assertThat(groupDevicesById.contains(mRightDevice)).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);

        assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();
        verify(mNativeInterface, times(1)).groupSetActive(groupId);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));

        reset(mNativeInterface);
        reset(mAudioManager);

        // Send a message to trigger disconnection completed to the right device
        injectAndVerifyDeviceDisconnected(mRightDevice);

        // Send a message to trigger disconnection completed to the left device
        injectAndVerifyDeviceDisconnected(mLeftDevice);

        reset(mNativeInterface);
        reset(mAudioManager);

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        generateConnectionMessageFromNative(
                mLeftDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mLeftDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
        assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue();

        // Expect device to be inactive
        audioConfChangedEvent.valueInt5 = 0;
        mService.messageFromNative(audioConfChangedEvent);

        generateConnectionMessageFromNative(
                mRightDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mRightDevice))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
        assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue();

        // Expect device to be active
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        verify(mNativeInterface, times(1)).groupSetActive(groupId);

        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        any(BluetoothDevice.class),
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));
    }

    /** Test setting allowed contexts for active group */
    @Test
    public void testSetAllowedContextsForActiveGroup() {