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

Commit c47c7b54 authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Block newly connected device to become active when broadcasting

When the device is broadcasting audio, we need to block the newly
connected LEA and classic devices to automatically become active.
Instead UI will pop a dialog for user to select the next action.

Bug: 308171251
Bug: 308170200
Test: atest ActiveDeviceManagerTest AudioRoutingManagerTest
Test: manual test with classic & LEA BT devices
Change-Id: Ice378c71edf8f6cd5f02de4de0dc0489c8b41c64
parent cddb8824
Loading
Loading
Loading
Loading
+65 −1
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
@@ -111,6 +112,7 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
    private Handler mHandler = null;
    private final AudioManager mAudioManager;
    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
    private final FeatureFlags mFeatureFlags;

    private final Object mLock = new Object();
    @GuardedBy("mLock")
@@ -250,6 +252,17 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                return;
            }
            mA2dpConnectedDevices.add(device);
            if (isBroadcastingAudio()) {
                Log.i(
                        TAG,
                        "LE Audio Broadcast is streaming, skip setting A2dp device as active: "
                                + device);
                if (mPendingActiveDevice != null) {
                    mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                }
                return;
            }

            if (mHearingAidActiveDevices.isEmpty() && mLeHearingAidActiveDevice == null) {
                // New connected device: select it as active
                // Activate HFP and A2DP at the same time if both profile already connected.
@@ -314,6 +327,17 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                return;
            }
            mHfpConnectedDevices.add(device);
            if (isBroadcastingAudio()) {
                Log.i(
                        TAG,
                        "LE Audio Broadcast is streaming, skip setting Hfp device as active: "
                                + device);
                if (mPendingActiveDevice != null) {
                    mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                }
                return;
            }

            if (mHearingAidActiveDevices.isEmpty() && mLeHearingAidActiveDevice == null) {
                // New connected device: select it as active
                // Activate HFP and A2DP at the same time once both profile connected.
@@ -377,6 +401,14 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                return;
            }
            mHearingAidConnectedDevices.add(device);
            if (isBroadcastingAudio()) {
                Log.i(
                        TAG,
                        "LE Audio Broadcast is streaming, skip setting HearingAid device as "
                                + "active:  "
                                + device);
                return;
            }
            // New connected device: select it as active
            if (setHearingAidActiveDevice(device)) {
                setA2dpActiveDevice(null, true);
@@ -406,6 +438,14 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
            }

            mLeAudioConnectedDevices.add(device);
            if (isBroadcastingAudio()) {
                Log.i(
                        TAG,
                        "LE Audio Broadcast is streaming, skip setting le audio device as active: "
                                + device);
                return;
            }

            if (mHearingAidActiveDevices.isEmpty()
                    && mLeHearingAidActiveDevice == null
                    && mPendingLeHearingAidActiveDevice.isEmpty()) {
@@ -437,6 +477,14 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
                return;
            }
            mLeHearingAidConnectedDevices.add(device);
            if (isBroadcastingAudio()) {
                Log.i(
                        TAG,
                        "LE Audio Broadcast is streaming, skip setting Hap device as active: "
                                + device);
                return;
            }

            if (!mLeAudioConnectedDevices.contains(device)) {
                mPendingLeHearingAidActiveDevice.add(device);
            } else if (Objects.equals(mLeAudioActiveDevice, device)) {
@@ -763,9 +811,10 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        }
    }

    ActiveDeviceManager(AdapterService service, ServiceFactory factory) {
    ActiveDeviceManager(AdapterService service, ServiceFactory factory, FeatureFlags featureFlags) {
        mAdapterService = service;
        mDbManager = mAdapterService.getDatabase();
        mFeatureFlags = Objects.requireNonNull(featureFlags, "Feature Flags cannot be null");
        mFactory = factory;
        mAudioManager = service.getSystemService(AudioManager.class);
        mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback();
@@ -1190,6 +1239,21 @@ public class ActiveDeviceManager implements AdapterService.BluetoothStateCallbac
        return false;
    }

    /**
     * Checks if le audio broadcasting is ON
     *
     * @return {@code true} if is broadcasting audio, {@code false} otherwise
     */
    private boolean isBroadcastingAudio() {
        if (!mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) {
            // disable this if feature flag is false
            return false;
        }

        final LeAudioService leAudioService = mFactory.getLeAudioService();
        return leAudioService != null && !leAudioService.getAllBroadcastMetadata().isEmpty();
    }

    /**
     * Called when a wired audio device is connected.
     * It might be called multiple times each time a wired audio device is connected.
+4 −2
Original line number Diff line number Diff line
@@ -695,9 +695,11 @@ public class AdapterService extends Service {
        }

        if (featureFlags.audioRoutingCentralization()) {
            mActiveDeviceManager = new AudioRoutingManager(this, new ServiceFactory());
            mActiveDeviceManager =
                    new AudioRoutingManager(this, new ServiceFactory(), featureFlags);
        } else {
            mActiveDeviceManager = new ActiveDeviceManager(this, new ServiceFactory());
            mActiveDeviceManager =
                    new ActiveDeviceManager(this, new ServiceFactory(), featureFlags);
        }
        mActiveDeviceManager.start();

+3 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
@@ -761,8 +762,8 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {}
    }

    AudioRoutingManager(AdapterService service, ServiceFactory factory) {
        super(service, factory);
    AudioRoutingManager(AdapterService service, ServiceFactory factory, FeatureFlags featureFlags) {
        super(service, factory, featureFlags);
        mAdapterService = service;
        mDbManager = mAdapterService.getDatabase();
        mFactory = factory;
+66 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.Context;
@@ -52,6 +54,7 @@ import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.flags.FakeFeatureFlagsImpl;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
@@ -95,6 +98,7 @@ public class ActiveDeviceManagerTest {
    private boolean mOriginalDualModeAudioState;
    private TestDatabaseManager mDatabaseManager;
    private TestLooper mTestLooper;
    private FakeFeatureFlagsImpl mFakeFlagsImpl;

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -116,7 +120,10 @@ public class ActiveDeviceManagerTest {
        mTestLooper.startAutoDispatch();
        TestUtils.setAdapterService(mAdapterService);

        mDatabaseManager = new TestDatabaseManager(mAdapterService, new FakeFeatureFlagsImpl());
        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false);
        mDatabaseManager = new TestDatabaseManager(mAdapterService, mFakeFlagsImpl);

        when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
        when(mAdapterService.getSystemServiceName(AudioManager.class))
                .thenReturn(Context.AUDIO_SERVICE);
@@ -126,7 +133,8 @@ public class ActiveDeviceManagerTest {
        when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
        when(mServiceFactory.getLeAudioService()).thenReturn(mLeAudioService);

        mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory);
        mActiveDeviceManager =
                new ActiveDeviceManager(mAdapterService, mServiceFactory, mFakeFlagsImpl);
        mActiveDeviceManager.start();
        mAdapter = BluetoothAdapter.getDefaultAdapter();

@@ -1173,6 +1181,62 @@ public class ActiveDeviceManagerTest {
        verify(mHearingAidService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
    }

    /**
     * Verifies if Le Audio Broadcast is streaming, connected a2dp device should not be set as
     * active.
     */
    @Test
    public void a2dpConnectedWhenBroadcasting_notSetA2dpActive() {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, true);
        final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class);
        when(mLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList);
        a2dpConnected(mA2dpDevice, false);
        verify(mA2dpService, never()).setActiveDevice(any());
        a2dpConnected(mA2dpDevice, true);
        verify(mA2dpService, never()).setActiveDevice(any());
    }

    /**
     * Verifies if Le Audio Broadcast is streaming, connected headset device should not be set as
     * active.
     */
    @Test
    public void headsetConnectedWhenBroadcasting_notSetHeadsetActive() {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, true);
        final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class);
        when(mLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList);
        headsetConnected(mHeadsetDevice, false);
        verify(mHeadsetService, never()).setActiveDevice(any());
        headsetConnected(mHeadsetDevice, true);
        verify(mHeadsetService, never()).setActiveDevice(any());
    }

    /**
     * Verifies if Le Audio Broadcast is streaming, connected hearing aid device should not be set
     * as active.
     */
    @Test
    public void hearingAidConnectedWhenBroadcasting_notSetHearingAidActive() {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, true);
        final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class);
        when(mLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList);
        hearingAidConnected(mHearingAidDevice);
        verify(mHearingAidService, never()).setActiveDevice(any());
    }

    /**
     * Verifies if Le Audio Broadcast is streaming, connected LE hearing aid device should not be
     * set as active.
     */
    @Test
    public void leHearingAidConnectedWhenBroadcasting_notSetLeHearingAidActive() {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, true);
        final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class);
        when(mLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList);
        leHearingAidConnected(mLeHearingAidDevice);
        verify(mLeAudioService, never()).setActiveDevice(any());
    }

    /**
     * Helper to indicate A2dp connected for a device.
     */
+5 −2
Original line number Diff line number Diff line
@@ -93,6 +93,7 @@ public class AudioRoutingManagerTest {
    private boolean mOriginalDualModeAudioState;
    private TestDatabaseManager mDatabaseManager;
    private TestLooper mTestLooper;
    private FakeFeatureFlagsImpl mFakeFlagsImpl;

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -114,7 +115,8 @@ public class AudioRoutingManagerTest {
        mTestLooper.startAutoDispatch();
        TestUtils.setAdapterService(mAdapterService);

        mDatabaseManager = new TestDatabaseManager(mAdapterService, new FakeFeatureFlagsImpl());
        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mDatabaseManager = new TestDatabaseManager(mAdapterService, mFakeFlagsImpl);

        when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
        when(mAdapterService.getSystemServiceName(AudioManager.class))
@@ -125,7 +127,8 @@ public class AudioRoutingManagerTest {
        when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
        when(mServiceFactory.getLeAudioService()).thenReturn(mLeAudioService);

        mAudioRoutingManager = new AudioRoutingManager(mAdapterService, mServiceFactory);
        mAudioRoutingManager =
                new AudioRoutingManager(mAdapterService, mServiceFactory, mFakeFlagsImpl);
        mAudioRoutingManager.start();
        mAdapter = BluetoothAdapter.getDefaultAdapter();