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

Commit de05b661 authored by Yiyi Shen's avatar Yiyi Shen Committed by Android (Google) Code Review
Browse files

Merge "[Audiosharing] Fix the primary buds auto pick logic in hysteresis mode" into main

parents 47026313 ee1966c2
Loading
Loading
Loading
Loading
+88 −36
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.settingslib.bluetooth;

import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;

import static java.util.stream.Collectors.toList;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.bluetooth.BluetoothAdapter;
@@ -64,6 +66,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@@ -84,6 +87,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
    public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE";
    public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING";
    public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING";
    public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID =
            "bluetooth_le_broadcast_primary_device_group_id";
    public static final int BROADCAST_STATE_UNKNOWN = 0;
    public static final int BROADCAST_STATE_ON = 1;
    public static final int BROADCAST_STATE_OFF = 2;
@@ -1121,6 +1126,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {

    /** Update fallback active device if needed. */
    public void updateFallbackActiveDeviceIfNeeded() {
        if (isWorkProfile(mContext)) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile.");
            return;
        }
        if (mServiceBroadcast == null) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null");
            return;
@@ -1135,71 +1144,114 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
            return;
        }
        List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast();
        if (devicesInBroadcast.isEmpty()) {
        Map<Integer, List<BluetoothDevice>> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast();
        if (deviceGroupsInBroadcast.isEmpty()) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
            return;
        }
        List<BluetoothDevice> devices =
                BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
        BluetoothDevice targetDevice = null;
        // Find the earliest connected device in sharing session.
        int targetDeviceIdx = -1;
        for (BluetoothDevice device : devicesInBroadcast) {
            if (devices.contains(device)) {
                int idx = devices.indexOf(device);
                if (idx > targetDeviceIdx) {
                    targetDeviceIdx = idx;
                    targetDevice = device;
        int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
        int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast(
                mContext.getContentResolver());
        if (Flags.audioSharingHysteresisModeFix()) {
            int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId();
            if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
                    && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) {
                if (userPreferredPrimaryGroupId == fallbackActiveGroupId) {
                    Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already user preferred");
                    return;
                } else {
                    targetGroupId = userPreferredPrimaryGroupId;
                }
            }
            if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
                // If there is no user preferred primary device, set the earliest connected
                // device in sharing session as the fallback.
                targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast);
            }
        if (targetDevice == null) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
            return;
        } else {
            // Set the earliest connected device in sharing session as the fallback.
            targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast);
        }
        CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
        if (targetCachedDevice == null) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
        Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, target group id = " + targetGroupId);
        if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return;
        if (targetGroupId == fallbackActiveGroupId) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback");
            return;
        }
        int fallbackActiveGroupId = getFallbackActiveGroupId();
        if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
                && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
            Log.d(
                    TAG,
                    "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
                            + fallbackActiveGroupId);
        CachedBluetoothDevice targetCachedDevice = getMainDevice(
                deviceGroupsInBroadcast.get(targetGroupId));
        if (targetCachedDevice == null) {
            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find main device");
            return;
        }
        Log.d(
                TAG,
                "updateFallbackActiveDeviceIfNeeded, set active device: "
                        + targetDevice.getAnonymizedAddress());
                        + targetCachedDevice.getDevice());
        targetCachedDevice.setActive();
    }

    private List<BluetoothDevice> getDevicesInBroadcast() {
    @NonNull
    private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
        boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
        List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
        return connectedDevices.stream()
                .filter(
                        bluetoothDevice -> {
                        device -> {
                            List<BluetoothLeBroadcastReceiveState> sourceList =
                                    mServiceBroadcastAssistant.getAllSources(
                                            bluetoothDevice);
                                    mServiceBroadcastAssistant.getAllSources(device);
                            return !sourceList.isEmpty() && sourceList.stream().anyMatch(
                                    source -> hysteresisModeFixEnabled
                                            ? BluetoothUtils.isSourceMatched(source, mBroadcastId)
                                            : BluetoothUtils.isConnected(source));
                        })
                .collect(Collectors.toList());
                .collect(Collectors.groupingBy(
                        device -> BluetoothUtils.getGroupId(mDeviceManager.findDevice(device))));
    }

    private int getFallbackActiveGroupId() {
    private int getEarliestConnectedDeviceGroup(
            @NonNull Map<Integer, List<BluetoothDevice>> deviceGroups) {
        List<BluetoothDevice> devices =
                BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
        // Find the earliest connected device in sharing session.
        int targetDeviceIdx = -1;
        int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
        for (Map.Entry<Integer, List<BluetoothDevice>> entry : deviceGroups.entrySet()) {
            for (BluetoothDevice device : entry.getValue()) {
                if (devices.contains(device)) {
                    int idx = devices.indexOf(device);
                    if (idx > targetDeviceIdx) {
                        targetDeviceIdx = idx;
                        targetGroupId = entry.getKey();
                    }
                }
            }
        }
        return targetGroupId;
    }

    @Nullable
    private CachedBluetoothDevice getMainDevice(@Nullable List<BluetoothDevice> devices) {
        if (devices == null || devices.size() == 1) return null;
        List<CachedBluetoothDevice> cachedDevices =
                devices.stream()
                        .map(device -> mDeviceManager.findDevice(device))
                        .filter(Objects::nonNull)
                        .collect(toList());
        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
            if (!cachedDevice.getMemberDevice().isEmpty()) {
                return cachedDevice;
            }
        }
        CachedBluetoothDevice mainDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0);
        return mainDevice;
    }

    private int getUserPreferredPrimaryGroupId() {
        // TODO: use real key name in SettingsProvider
        return Settings.Secure.getInt(
                mContext.getContentResolver(),
                "bluetooth_le_broadcast_fallback_active_group_id",
                mContentResolver,
                BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID,
                BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
    }

+61 −84
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -36,6 +37,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyManager;

@@ -96,6 +98,8 @@ public class BluetoothEventManagerTest {
    private LocalBluetoothProfileManager mLocalProfileManager;
    @Mock
    private BluetoothUtils.ErrorListener mErrorListener;
    @Mock
    private LocalBluetoothLeBroadcast mBroadcast;

    private Context mContext;
    private Intent mIntent;
@@ -107,7 +111,7 @@ public class BluetoothEventManagerTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = RuntimeEnvironment.application;
        mContext = spy(RuntimeEnvironment.application);

        mBluetoothEventManager =
                new BluetoothEventManager(
@@ -208,28 +212,14 @@ public class BluetoothEventManagerTest {
     */
    @Test
    public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() {
        ShadowBluetoothAdapter shadowBluetoothAdapter =
                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
        when(broadcast.isProfileReady()).thenReturn(true);
        LocalBluetoothLeBroadcastAssistant assistant =
                mock(LocalBluetoothLeBroadcastAssistant.class);
        when(assistant.isProfileReady()).thenReturn(true);
        LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
        when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
        when(mBtManager.getProfileManager()).thenReturn(profileManager);
        setUpAudioSharing(/* enableFlag= */ false, /* enableFeature= */ true, /* enableProfile= */
                true, /* workProfile= */ false);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
        verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
    }

    /**
@@ -239,28 +229,14 @@ public class BluetoothEventManagerTest {
     */
    @Test
    public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() {
        ShadowBluetoothAdapter shadowBluetoothAdapter =
                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
        when(broadcast.isProfileReady()).thenReturn(true);
        LocalBluetoothLeBroadcastAssistant assistant =
                mock(LocalBluetoothLeBroadcastAssistant.class);
        when(assistant.isProfileReady()).thenReturn(true);
        LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
        when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
        when(mBtManager.getProfileManager()).thenReturn(profileManager);
        setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ false, /* enableProfile= */
                true, /* workProfile= */ false);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
        verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
    }

    /**
@@ -270,28 +246,14 @@ public class BluetoothEventManagerTest {
     */
    @Test
    public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() {
        ShadowBluetoothAdapter shadowBluetoothAdapter =
                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
        when(broadcast.isProfileReady()).thenReturn(false);
        LocalBluetoothLeBroadcastAssistant assistant =
                mock(LocalBluetoothLeBroadcastAssistant.class);
        when(assistant.isProfileReady()).thenReturn(true);
        LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
        when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
        when(mBtManager.getProfileManager()).thenReturn(profileManager);
        setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
                false, /* workProfile= */ false);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
        verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
    }

    /**
@@ -301,28 +263,31 @@ public class BluetoothEventManagerTest {
     */
    @Test
    public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() {
        ShadowBluetoothAdapter shadowBluetoothAdapter =
                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
        when(broadcast.isProfileReady()).thenReturn(true);
        LocalBluetoothLeBroadcastAssistant assistant =
                mock(LocalBluetoothLeBroadcastAssistant.class);
        when(assistant.isProfileReady()).thenReturn(true);
        LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
        when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
        when(mBtManager.getProfileManager()).thenReturn(profileManager);
        setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
                true, /* workProfile= */ false);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO);

        verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
        verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
    }

    /**
     * dispatchProfileConnectionStateChanged should not call {@link
     * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for
     * work profile.
     */
    @Test
    public void dispatchProfileConnectionStateChanged_workProfile_noUpdateFallbackDevice() {
        setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
                true, /* workProfile= */ true);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
    }

    /**
@@ -332,28 +297,40 @@ public class BluetoothEventManagerTest {
     */
    @Test
    public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() {
        setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */
                true, /* workProfile= */ false);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(mBroadcast).updateFallbackActiveDeviceIfNeeded();
    }

    private void setUpAudioSharing(boolean enableFlag, boolean enableFeature,
            boolean enableProfile, boolean workProfile) {
        if (enableFlag) {
            mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        } else {
            mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        }
        ShadowBluetoothAdapter shadowBluetoothAdapter =
                Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
                BluetoothStatusCodes.FEATURE_SUPPORTED);
        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
        LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
        when(broadcast.isProfileReady()).thenReturn(true);
        int code = enableFeature ? BluetoothStatusCodes.FEATURE_SUPPORTED
                : BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
        shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(code);
        shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(code);
        when(mBroadcast.isProfileReady()).thenReturn(enableProfile);
        LocalBluetoothLeBroadcastAssistant assistant =
                mock(LocalBluetoothLeBroadcastAssistant.class);
        when(assistant.isProfileReady()).thenReturn(true);
        when(assistant.isProfileReady()).thenReturn(enableProfile);
        LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
        when(profileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
        when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
        when(mBtManager.getProfileManager()).thenReturn(profileManager);
        mBluetoothEventManager.dispatchProfileConnectionStateChanged(
                mCachedBluetoothDevice,
                BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);

        verify(broadcast).updateFallbackActiveDeviceIfNeeded();
        UserManager userManager = mock(UserManager.class);
        when(mContext.getSystemService(UserManager.class)).thenReturn(userManager);
        when(userManager.isManagedProfile()).thenReturn(workProfile);
    }

    @Test