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

Commit 539a0794 authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

Merge "le_audio: Handle overlapping connected LE Audio devices" into main

parents 7f32aabe 2402df63
Loading
Loading
Loading
Loading
+97 −7
Original line number Diff line number Diff line
@@ -147,6 +147,9 @@ public class LeAudioService extends ProfileService {
                    .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000)
                    .build();

    /* 5 seconds timeout for Broadcast streaming state transition */
    private static final int DIALING_OUT_TIMEOUT_MS = 5000;

    private AdapterService mAdapterService;
    private DatabaseManager mDatabaseManager;
    private HandlerThread mStateMachinesThread;
@@ -167,6 +170,7 @@ public class LeAudioService extends ProfileService {
    boolean mBluetoothEnabled = false;
    BluetoothDevice mHfpHandoverDevice = null;
    LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null;
    private DialingOutTimeoutEvent mDialingOutTimeoutEvent = null;
    @VisibleForTesting
    AudioManager mAudioManager;
    LeAudioTmapGattServer mTmapGattServer;
@@ -428,6 +432,8 @@ public class LeAudioService extends ProfileService {
        mAwaitingBroadcastCreateResponse = false;
        mIsSourceStreamMonitorModeEnabled = false;

        clearBroadcastTimeoutCallback();

        mHandler.removeCallbacks(this::init);
        removeActiveDevice(false);

@@ -1084,6 +1090,11 @@ public class LeAudioService extends ProfileService {
            return;
        }
        if (DBG) Log.d(TAG, "startBroadcast");

        /* Start timeout to recover from stucked/error start Broadcast operation */
        mDialingOutTimeoutEvent = new DialingOutTimeoutEvent();
        mHandler.postDelayed(mDialingOutTimeoutEvent, DIALING_OUT_TIMEOUT_MS);

        mLeAudioBroadcasterNativeInterface.startBroadcast(broadcastId);
    }

@@ -1750,12 +1761,19 @@ public class LeAudioService extends ProfileService {
     * @param newDevice new supported broadcast audio device
     * @param previousDevice previous no longer supported broadcast audio device
     */
    /* TODO implement unicast overlap with connected unicast device */
    private void updateBroadcastActiveDevice(
            BluetoothDevice newDevice,
            BluetoothDevice previousDevice,
            boolean suppressNoisyIntent) {
        mActiveBroadcastAudioDevice = newDevice;
        if (DBG) {
            Log.d(
                    TAG,
                    "updateBroadcastActiveDevice: newDevice: "
                            + newDevice
                            + ", previousDevice: "
                            + previousDevice);
        }
        mAudioManager.handleBluetoothActiveDeviceChanged(
                newDevice, previousDevice, getBroadcastProfile(suppressNoisyIntent));
    }
@@ -2272,6 +2290,16 @@ public class LeAudioService extends ProfileService {
                            || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) {
                leaveConnectedInputDevice = true;
                newDirections |= AUDIO_DIRECTION_INPUT_BIT;

                /* Update Broadcast device before streaming state in handover case to avoid switch
                 * to non LE Audio device in Audio Manager e.g. Phone Speaker.
                 */
                BluetoothDevice device =
                        mAdapterService.getDeviceFromByte(
                                Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF"));
                if (!device.equals(mActiveBroadcastAudioDevice)) {
                    updateBroadcastActiveDevice(device, mActiveBroadcastAudioDevice, true);
                }
            }

            descriptor.mIsActive = false;
@@ -2558,8 +2586,6 @@ public class LeAudioService extends ProfileService {
            updateFallbackUnicastGroupIdForBroadcast(LE_AUDIO_GROUP_ID_INVALID);
            updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false);
            return;
        } else {
            updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true);
        }

        if (DBG) {
@@ -2573,6 +2599,21 @@ public class LeAudioService extends ProfileService {
        setActiveDevice(unicastDevice);
    }

    void clearBroadcastTimeoutCallback() {
        if (mHandler == null) {
            Log.e(TAG, "No callback handler");
            return;
        }

        /* Timeout callback already cleared */
        if (mDialingOutTimeoutEvent == null) {
            return;
        }

        mHandler.removeCallbacks(mDialingOutTimeoutEvent);
        mDialingOutTimeoutEvent = null;
    }

    // Suppressed since this is part of a local process
    @SuppressLint("AndroidFrameworkRequiresPermission")
    void messageFromNative(LeAudioStackEvent stackEvent) {
@@ -2786,6 +2827,11 @@ public class LeAudioService extends ProfileService {
            switch (groupStatus) {
                case LeAudioStackEvent.GROUP_STATUS_ACTIVE: {
                    handleGroupTransitToActive(groupId);

                    /* Clear possible exposed broadcast device after activating unicast */
                    if (mActiveBroadcastAudioDevice != null) {
                        updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true);
                    }
                    break;
                }
                case LeAudioStackEvent.GROUP_STATUS_INACTIVE: {
@@ -2831,6 +2877,20 @@ public class LeAudioService extends ProfileService {

            } else {
                // TODO: Improve reason reporting or extend the native stack event with reason code
                Log.e(
                        TAG,
                        "EVENT_TYPE_BROADCAST_CREATED: Failed to create broadcast: " + broadcastId);

                /* Disconnect Broadcast device which was connected to avoid non LE Audio sound
                 * leak in handover scenario.
                 */
                if ((mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID)
                        && mCreateBroadcastQueue.isEmpty()
                        && (!Objects.equals(device, mActiveBroadcastAudioDevice))) {
                    clearBroadcastTimeoutCallback();
                    updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false);
                }

                notifyBroadcastStartFailed(broadcastId, BluetoothStatusCodes.ERROR_UNKNOWN);
            }

@@ -2926,11 +2986,21 @@ public class LeAudioService extends ProfileService {
                        bassClientService.suspendReceiversSourceSynchronization(broadcastId);
                    }

                    // Notify audio manager
                    updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, true);

                    /* Restore the Unicast stream from before the Broadcast was started. */
                    if (mUnicastGroupIdDeactivatedForBroadcastTransition
                            != LE_AUDIO_GROUP_ID_INVALID) {
                        transitionFromBroadcastToUnicast();
                    } else {
                        // Notify audio manager
                        if (mBroadcastDescriptors.values().stream()
                                .noneMatch(
                                        d ->
                                                d.mState.equals(
                                                        LeAudioStackEvent
                                                                .BROADCAST_STATE_STREAMING))) {
                            updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false);
                        }
                    }
                    break;
                case LeAudioStackEvent.BROADCAST_STATE_STOPPING:
                    if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping.");
@@ -2942,6 +3012,8 @@ public class LeAudioService extends ProfileService {
                    notifyPlaybackStarted(broadcastId,
                            BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);

                    clearBroadcastTimeoutCallback();

                    if (previousState == LeAudioStackEvent.BROADCAST_STATE_PAUSED) {
                        if (bassClientService != null) {
                            bassClientService.resumeReceiversSourceSynchronization();
@@ -4250,6 +4322,24 @@ public class LeAudioService extends ProfileService {
        return audioFrameworkCalls;
    }

    class DialingOutTimeoutEvent implements Runnable {
        @Override
        public void run() {
            Log.w(TAG, "Failed to start Broadcast in time");

            mDialingOutTimeoutEvent = null;

            if (getLeAudioService() == null) {
                Log.e(TAG, "DialingOutTimeoutEvent: No LE Audio service");
                return;
            }

            if (mActiveBroadcastAudioDevice != null) {
                updateBroadcastActiveDevice(null, mActiveBroadcastAudioDevice, false);
            }
        }
    }

    /**
     * Binder object: must be a static class or memory leak may occur
     */
+75 −81
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Looper;
import android.os.ParcelUuid;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -37,6 +38,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.Utils;
import com.android.bluetooth.bass_client.BassClientService;
import com.android.bluetooth.btservice.ActiveDeviceManager;
import com.android.bluetooth.btservice.AdapterService;
@@ -51,6 +53,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

@@ -67,6 +70,8 @@ public class LeAudioBroadcastServiceTest {

    private BluetoothAdapter mAdapter;
    private BluetoothDevice mDevice;
    private BluetoothDevice mBroadcastDevice;

    private Context mTargetContext;
    private LeAudioService mService;
    private LeAudioIntentReceiver mLeAudioIntentReceiver;
@@ -228,7 +233,9 @@ public class LeAudioBroadcastServiceTest {
        mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter);

        mDevice = TestUtils.getTestDevice(mAdapter, 0);
        when(mLeAudioBroadcasterNativeInterface.getDevice(any(byte[].class))).thenReturn(mDevice);
        mBroadcastDevice = TestUtils.getTestDevice(mAdapter, 1);
        when(mAdapterService.getDeviceFromByte(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF")))
                .thenReturn(mBroadcastDevice);

        mIntentQueue = new LinkedBlockingQueue<Intent>();
    }
@@ -794,12 +801,7 @@ public class LeAudioBroadcastServiceTest {
                mOnBroadcastStartFailedReason);
    }

    @Test
    public void testInCallDrivenBroadcastSwitch() {
        int groupId = 1;
        int broadcastId = 243;
        byte[] code = {0x00, 0x01, 0x00, 0x02};

    private void prepareHandoverStreamingBroadcast(int groupId, int broadcastId, byte[] code) {
        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES);

@@ -813,6 +815,14 @@ public class LeAudioBroadcastServiceTest {
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(create_event);

        /* Verify Unicast input and output devices changed from null to mDevice */
        verify(mAudioManager, times(2))
                .handleBluetoothActiveDeviceChanged(
                        eq(mDevice), eq(null), any(BluetoothProfileConnectionInfo.class));
        Mockito.clearInvocations(mAudioManager);

        mService.notifyActiveDeviceChanged(mDevice);

        /* Prepare create broadcast */
        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
@@ -823,6 +833,10 @@ public class LeAudioBroadcastServiceTest {
        BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1);
        mService.createBroadcast(settings);

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

        /* Active group should become inactive */
        int activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID);
@@ -833,6 +847,11 @@ public class LeAudioBroadcastServiceTest {
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(create_event);

        /* Only one Unicast device should become inactive due to Sink monitor mode */
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class));
        Mockito.clearInvocations(mAudioManager);
        List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings();

        int[] expectedQualityArray =
@@ -850,6 +869,8 @@ public class LeAudioBroadcastServiceTest {
                        eq(settings.getPublicBroadcastMetadata().getRawMetadata()),
                        eq(expectedQualityArray),
                        eq(expectedDataArray));
        verify(mLeAudioNativeInterface, times(1))
                .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK), eq(true));

        activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup);
@@ -865,9 +886,19 @@ public class LeAudioBroadcastServiceTest {

        /* Switch to active streaming */
        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE);
        create_event.device = mBroadcastDevice;
        create_event.valueInt1 = broadcastId;
        create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING;
        mService.messageFromNative(create_event);
    }

    @Test
    public void testInCallDrivenBroadcastSwitch() {
        int groupId = 1;
        int broadcastId = 243;
        byte[] code = {0x00, 0x01, 0x00, 0x02};

        prepareHandoverStreamingBroadcast(groupId, broadcastId, code);

        /* Imitate setting device in call */
        mService.setInCall(true);
@@ -883,13 +914,21 @@ public class LeAudioBroadcastServiceTest {

        verify(mLeAudioNativeInterface, times(1)).setInCall(eq(true));

        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        LeAudioStackEvent create_event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        create_event.valueInt1 = groupId;
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(create_event);

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

        /* Active group should become the one that was active before broadcasting */
        activeGroup = mService.getActiveGroupId();
        int activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(activeGroup, groupId);

        /* Imitate setting device not in call */
@@ -903,6 +942,14 @@ public class LeAudioBroadcastServiceTest {
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(create_event);

        /* Only one Unicast device should become inactive due to Sink monitor mode */
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class));
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class));

        /* Verify if broadcast is auto-started on start */
        verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId));
    }
@@ -913,82 +960,14 @@ public class LeAudioBroadcastServiceTest {
        int broadcastId = 243;
        byte[] code = {0x00, 0x01, 0x00, 0x02};

        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES);

        mService.mBroadcastCallbacks.register(mCallbacks);

        prepareConnectedUnicastDevice(groupId);

        LeAudioStackEvent create_event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        create_event.valueInt1 = groupId;
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(create_event);

        /* Prepare create broadcast */
        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("ENG");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();

        BluetoothLeBroadcastSettings settings = buildBroadcastSettingsFromMetadata(meta, code, 1);
        mService.createBroadcast(settings);

        /* Active group should become inactive */
        int activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(activeGroup, LE_AUDIO_GROUP_ID_INVALID);

        /* Imitate group inactivity to cause create broadcast */
        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        create_event.valueInt1 = groupId;
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(create_event);

        List<BluetoothLeBroadcastSubgroupSettings> settingsList = settings.getSubgroupSettings();

        int[] expectedQualityArray =
                settingsList.stream().mapToInt(setting -> setting.getPreferredQuality()).toArray();
        byte[][] expectedDataArray =
                settingsList.stream()
                        .map(setting -> setting.getContentMetadata().getRawMetadata())
                        .toArray(byte[][]::new);

        verify(mLeAudioBroadcasterNativeInterface, times(1))
                .createBroadcast(
                        eq(true),
                        eq(TEST_BROADCAST_NAME),
                        eq(settings.getBroadcastCode()),
                        eq(settings.getPublicBroadcastMetadata().getRawMetadata()),
                        eq(expectedQualityArray),
                        eq(expectedDataArray));
        verify(mLeAudioNativeInterface, times(1))
                .setUnicastMonitorMode(eq(LeAudioStackEvent.DIRECTION_SINK),eq(true));

        activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(LE_AUDIO_GROUP_ID_INVALID, activeGroup);

        /* Check if broadcast is started automatically when created */
        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED);
        create_event.valueInt1 = broadcastId;
        create_event.valueBool1 = true;
        mService.messageFromNative(create_event);

        /* Verify if broadcast is auto-started on start */
        verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId));

        /* Switch to active streaming */
        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE);
        create_event.valueInt1 = broadcastId;
        create_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STREAMING;
        mService.messageFromNative(create_event);
        prepareHandoverStreamingBroadcast(groupId, broadcastId, code);

        /* Verify if broadcast is auto-started on start */
        verify(mLeAudioBroadcasterNativeInterface, times(1)).startBroadcast(eq(broadcastId));

        /* Imitate group change request by Bluetooth Sink HAL resume request */
        create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS);
        LeAudioStackEvent create_event =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS);
        create_event.valueInt1 = LeAudioStackEvent.DIRECTION_SINK;
        create_event.valueInt2 = LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED;
        mService.messageFromNative(create_event);
@@ -1007,8 +986,15 @@ public class LeAudioBroadcastServiceTest {
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(create_event);

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

        /* Active group should become the one that was active before broadcasting */
        activeGroup = mService.getActiveGroupId();
        int activeGroup = mService.getActiveGroupId();
        Assert.assertEquals(activeGroup, groupId);

        /* Imitate group change request by Bluetooth Sink HAL suspend request */
@@ -1025,6 +1011,14 @@ public class LeAudioBroadcastServiceTest {
        create_event.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(create_event);

        /* Only one Unicast device should become inactive due to Sink monitor mode */
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mDevice), any(BluetoothProfileConnectionInfo.class));
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(mBroadcastDevice), eq(null), any(BluetoothProfileConnectionInfo.class));

        /* Verify if broadcast is auto-started on start */
        verify(mLeAudioBroadcasterNativeInterface, times(2)).startBroadcast(eq(broadcastId));
    }