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

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

leaudio: Additional improvement to handle no available context types

When no context is available, LeAudio device will not be able to stream
anything.
This patch makes sure that ActiveDeviceManager will not use
LeAudioDevice when it is not available for streaming.
Also, native code makes sure to update Java with available contexts
before notifing about device being Connected. In this way, when
ActiveDeviceManager gets connected callback, it will have all the
information to decide if device can be used for streaming or not.

Bug: 333830497
Bug: 314677565
Test: atest LeAudioServiceTest ActiveDeviceManagerTest
bluetooth_le_audio_client_test
Flag: Exempt, easy fix, regression tested with unit tests, new unit test
added

Change-Id: Iace0bbe278f8fc99e8f8b0ffda888538dda12ee1
parent fb721fae
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -422,6 +422,11 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                return;
            }

            if (!leAudioService.isGroupAvailableForStream(leAudioService.getGroupId(device))) {
                Log.i(TAG, "LE Audio device is not available for streaming now." + device);
                return;
            }

            if (mHearingAidActiveDevices.isEmpty()
                    && mLeHearingAidActiveDevice == null
                    && mPendingLeHearingAidActiveDevice.isEmpty()) {
+40 −10
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
import static com.android.bluetooth.flags.Flags.leaudioApiSynchronizedBlockFix;
import static com.android.bluetooth.flags.Flags.leaudioGettingActiveStateSupport;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

@@ -1949,20 +1948,30 @@ public class LeAudioService extends ProfileService {
    /**
     * Set the active device group.
     *
     * @param hasFallbackDevice hasFallbackDevice whether any fallback device exists when
     *                          {@code device} is null.
     * @param hasFallbackDevice hasFallbackDevice whether any fallback device exists when {@code
     *     device} is null.
     */
    private void setActiveGroupWithDevice(BluetoothDevice device, boolean hasFallbackDevice) {
    private boolean setActiveGroupWithDevice(BluetoothDevice device, boolean hasFallbackDevice) {
        int groupId = LE_AUDIO_GROUP_ID_INVALID;

        if (device != null) {
            LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
            if (descriptor == null) {
                Log.e(TAG, "setActiveGroupWithDevice: No valid descriptor for device: " + device);
                return;
                return false;
            }

            groupId = descriptor.mGroupId;

            if (!isGroupAvailableForStream(groupId)) {
                Log.e(
                        TAG,
                        "setActiveGroupWithDevice: groupId "
                                + groupId
                                + " is not available for streaming");
                return false;
            }

            clearInactiveDueToContextTypeFlags();
        }

@@ -1987,7 +1996,7 @@ public class LeAudioService extends ProfileService {
            // If broadcast is ongoing and need to update unicast fallback active group
            // we need to update the cached group id and skip changing the active device
            updateFallbackUnicastGroupIdForBroadcast(groupId);
            return;
            return true;
        }

        LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(currentlyActiveGroupId);
@@ -2006,7 +2015,7 @@ public class LeAudioService extends ProfileService {
                                + mExposedActiveDevice);
                sentActiveDeviceChangeIntent(mExposedActiveDevice);
            }
            return;
            return true;
        }

        if (currentlyActiveGroupId != LE_AUDIO_GROUP_ID_INVALID
@@ -2017,7 +2026,7 @@ public class LeAudioService extends ProfileService {

        if (!mLeAudioNativeIsInitialized) {
            Log.e(TAG, "Le Audio not initialized properly.");
            return;
            return false;
        }

        if (Flags.leaudioGettingActiveStateSupport()) {
@@ -2040,6 +2049,7 @@ public class LeAudioService extends ProfileService {
             */
            handleGroupTransitToInactive(currentlyActiveGroupId);
        }
        return true;
    }

    /**
@@ -2090,8 +2100,7 @@ public class LeAudioService extends ProfileService {
                }
            }
        }
        setActiveGroupWithDevice(device, false);
        return true;
        return setActiveGroupWithDevice(device, false);
    }

    /**
@@ -3718,6 +3727,27 @@ public class LeAudioService extends ProfileService {
        }
    }

    /**
     * Check if group is available for streaming. If there is no available context types then group
     * is not available for streaming.
     *
     * @param groupId groupid
     * @return true if available, false otherwise
     */
    public boolean isGroupAvailableForStream(int groupId) {
        mGroupReadLock.lock();
        try {
            LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
            if (descriptor == null) {
                Log.e(TAG, "getGroupId: No valid descriptor for groupId: " + groupId);
                return false;
            }
            return descriptor.mAvailableContexts != 0;
        } finally {
            mGroupReadLock.unlock();
        }
    }

    /**
     * Set the user application ccid along with used context type
     * @param userUuid user uuid
+35 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.bluetooth.btservice;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
@@ -156,6 +157,7 @@ public class ActiveDeviceManagerTest {
        when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true);

        when(mLeAudioService.getLeadDevice(mLeAudioDevice)).thenReturn(mLeAudioDevice);
@@ -689,15 +691,25 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void onlyLeAudioConnected_setHeadsetActive() {
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
    }

    /** LE Audio is connected but is not ready for stream (no available context types). */
    @Test
    public void leAudioConnected_notReadyForStream() {
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(false);
        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, never()).setActiveDevice(mLeAudioDevice);
    }

    /**
     * Two LE Audio are connected. Should set the second one active.
     */
    @Test
    public void secondLeAudioConnected_setSecondLeAudioActive() {
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);

@@ -710,6 +722,7 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void lastLeAudioDisconnected_clearLeAudioActive() {
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);

@@ -722,6 +735,7 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void leAudioActiveDeviceSelected_setActive() {
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);

@@ -842,6 +856,7 @@ public class ActiveDeviceManagerTest {
    @Test
    public void leAudioSetConnectedThenNotActiveOneDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
@@ -866,6 +881,7 @@ public class ActiveDeviceManagerTest {
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
@@ -893,6 +909,7 @@ public class ActiveDeviceManagerTest {
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_hasFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
@@ -914,6 +931,8 @@ public class ActiveDeviceManagerTest {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        leAudioConnected(mLeAudioDevice);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);

@@ -948,6 +967,8 @@ public class ActiveDeviceManagerTest {
        a2dpConnected(mA2dpDevice, false);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);

        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mLeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);

@@ -990,6 +1011,7 @@ public class ActiveDeviceManagerTest {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mA2dpService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
        when(mHearingAidService.removeActiveDevice(anyBoolean())).thenReturn(true);

@@ -1019,6 +1041,9 @@ public class ActiveDeviceManagerTest {
    public void onlyLeHearingAidConnected_setLeAudioActive() {
        leHearingAidConnected(mLeHearingAidDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());

        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        verify(mLeAudioService, never()).setActiveDevice(mLeHearingAidDevice);

        leAudioConnected(mLeHearingAidDevice);
@@ -1032,6 +1057,9 @@ public class ActiveDeviceManagerTest {
    @Test
    public void leAudioConnectedAfterLeHearingAid_setLeAudioActiveShouldNotBeCalled() {
        leHearingAidConnected(mLeHearingAidDevice);

        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mLeHearingAidDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeHearingAidDevice);

@@ -1049,6 +1077,7 @@ public class ActiveDeviceManagerTest {
    public void activeDeviceChange_withHearingAidLeHearingAidAndA2dpDevices() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mHearingAidService.removeActiveDevice(anyBoolean())).thenReturn(true);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        hearingAidConnected(mHearingAidDevice);
        verify(mHearingAidService, timeout(TIMEOUT_MS)).setActiveDevice(mHearingAidDevice);
@@ -1079,6 +1108,7 @@ public class ActiveDeviceManagerTest {
    public void dualModeAudioDeviceConnected_withDualModeFeatureDisabled() {
        // Turn off the dual mode audio flag
        Utils.setDualModeAudioStateForTesting(false);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        // Ensure we remove the LEA active device when classic audio profiles are made active
        a2dpConnected(mDualModeAudioDevice, true);
@@ -1108,6 +1138,8 @@ public class ActiveDeviceManagerTest {
        // Turn on the dual mode audio flag
        Utils.setDualModeAudioStateForTesting(true);
        reset(mLeAudioService);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        when(mAdapterService.isAllSupportedClassicAudioProfilesActive(mDualModeAudioDevice))
                .thenReturn(false);

@@ -1120,6 +1152,7 @@ public class ActiveDeviceManagerTest {
        Assert.assertNull(mActiveDeviceManager.getHfpActiveDevice());

        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true);
        when(mLeAudioService.getLeadDevice(mDualModeAudioDevice)).thenReturn(mDualModeAudioDevice);

@@ -1161,6 +1194,7 @@ public class ActiveDeviceManagerTest {
        when(mA2dpService.setActiveDevice(any())).thenReturn(false);
        when(mHearingAidService.setActiveDevice(any())).thenReturn(false);
        when(mLeAudioService.setActiveDevice(any())).thenReturn(false);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);

        leAudioConnected(mDualModeAudioDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
@@ -1274,6 +1308,7 @@ public class ActiveDeviceManagerTest {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES);
        final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class);
        when(mLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList);
        when(mLeAudioService.isGroupAvailableForStream(anyInt())).thenReturn(true);
        leHearingAidConnected(mLeHearingAidDevice);
        verify(mLeAudioService, never()).setActiveDevice(any());
    }
+47 −5
Original line number Diff line number Diff line
@@ -1348,6 +1348,38 @@ public class LeAudioServiceTest {
                .isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
    }

    /** Test setting active device group with not available contexts */
    @Test
    public void testSetActiveDeviceGroupWithNoContextTypes() {
        int groupId = 1;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 0;

        // Not connected device
        assertThat(mService.setActiveDevice(mSingleDevice)).isFalse();

        // Connected device
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);

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

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

    /** Test switching active groups */
    @Test
    public void testSwitchActiveGroups() {
@@ -1580,7 +1612,7 @@ public class LeAudioServiceTest {
        nodeStatusChangedEvent.valueInt2 = nodeStatus;
        mService.messageFromNative(nodeStatusChangedEvent);

        assertThat(mService.setActiveDevice(mSingleDevice)).isTrue();
        assertThat(mService.setActiveDevice(mSingleDevice)).isFalse();

        // Add location support
        LeAudioStackEvent audioConfChangedEvent =
@@ -1593,6 +1625,8 @@ public class LeAudioServiceTest {
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

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

        // Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
@@ -2043,7 +2077,7 @@ public class LeAudioServiceTest {
                memberDevice = mRightDevice;
        }

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

        //Add location support
        LeAudioStackEvent audioConfChangedEvent =
@@ -2055,6 +2089,8 @@ public class LeAudioServiceTest {
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

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

        //Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
@@ -2114,7 +2150,7 @@ public class LeAudioServiceTest {
                memberDevice = mRightDevice;
        }

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

        //Add location support
        LeAudioStackEvent audioConfChangedEvent =
@@ -2126,6 +2162,8 @@ public class LeAudioServiceTest {
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

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

        //Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
@@ -2179,7 +2217,7 @@ public class LeAudioServiceTest {
        connectTestDevice(mLeftDevice, groupId);
        connectTestDevice(mRightDevice, groupId);

        assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();
        assertThat(mService.setActiveDevice(mLeftDevice)).isFalse();

        ArgumentCaptor<BluetoothProfileConnectionInfo> profileInfo =
                        ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);
@@ -2187,6 +2225,8 @@ public class LeAudioServiceTest {
        //Add location support.
        injectAudioConfChanged(groupId, availableContexts, direction);

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

        doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId);
        //Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
@@ -2471,7 +2511,7 @@ public class LeAudioServiceTest {
        nodeStatusChangedEvent.valueInt2 = nodeStatus;
        mService.messageFromNative(nodeStatusChangedEvent);

        assertThat(mService.setActiveDevice(mSingleDevice)).isTrue();
        assertThat(mService.setActiveDevice(mSingleDevice)).isFalse();

        // Add location support
        LeAudioStackEvent audioConfChangedEvent =
@@ -2484,6 +2524,8 @@ public class LeAudioServiceTest {
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

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

        // Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
+23 −14
Original line number Diff line number Diff line
@@ -3257,8 +3257,6 @@ class LeAudioClientImpl : public LeAudioClient {
      L2CA_LockBleConnParamsForProfileConnection(leAudioDevice->address_,
                                                 false);
    }
    callbacks_->OnConnectionState(ConnectionState::CONNECTED,
                                  leAudioDevice->address_);

    if (leAudioDevice->GetConnectionState() ==
            DeviceConnectState::CONNECTED_BY_USER_GETTING_READY &&
@@ -3273,11 +3271,23 @@ class LeAudioClientImpl : public LeAudioClient {
        ConnectionState::CONNECTED,
        bluetooth::le_audio::ConnectionStatus::SUCCESS);

    if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
    if (leAudioDevice->group_id_ == bluetooth::groups::kGroupUnknown) {
      log::warn(" LeAudio device {} connected with no group",
                ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
      callbacks_->OnConnectionState(ConnectionState::CONNECTED,
                                    leAudioDevice->address_);
      return;
    }

    LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_);
    if (group) {
      UpdateLocationsAndContextsAvailability(group);
    }

    /* Notify connected after contexts are notified */
    callbacks_->OnConnectionState(ConnectionState::CONNECTED,
                                  leAudioDevice->address_);

    AttachToStreamingGroupIfNeeded(leAudioDevice);

    if (reconnection_mode_ == BTM_BLE_BKG_CONNECT_TARGETED_ANNOUNCEMENTS) {
@@ -3287,7 +3297,6 @@ class LeAudioClientImpl : public LeAudioClient {
      group->AddToAllowListNotConnectedGroupMembers(gatt_if_);
    }
  }
  }

  bool IsAseAcceptingAudioData(struct ase* ase) {
    if (ase == nullptr) return false;