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

Commit 7cdf85a0 authored by Rahul Sabnis's avatar Rahul Sabnis
Browse files

Updates active device policy to support dual mode audio

Also only makes LEA active when all supported classic audio profiles are
active when the dual mode feature is enabled for DuMo audio devices

Tag: #feature
Bug: 265077412
Test: Manual
Change-Id: I865eff9e40791f28d09cfaa82d8bad76e399e90d
parent b634177f
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -97,7 +97,7 @@ public final class Utils {

    private static final String ENABLE_DUAL_MODE_AUDIO =
            "persist.bluetooth.enable_dual_mode_audio";
    private static final boolean sDualModeEnabled =
    private static boolean sDualModeEnabled =
            SystemProperties.getBoolean(ENABLE_DUAL_MODE_AUDIO, false);;

    private static final String KEY_TEMP_ALLOW_LIST_DURATION_MS = "temp_allow_list_duration_ms";
@@ -148,6 +148,15 @@ public final class Utils {
        return sDualModeEnabled;
    }

    /**
     * Only exposed for testing, do not invoke this method outside of tests.
     * @param enabled true if the dual mode state is enabled, false otherwise
     */
    public static void setDualModeAudioStateForTesting(boolean enabled) {
        Log.i(TAG, "Updating dual mode audio state for testing to: " + enabled);
        sDualModeEnabled = enabled;
    }

    public static @Nullable String getName(@Nullable BluetoothDevice device) {
        final AdapterService service = AdapterService.getAdapterService();
        if (service != null && device != null) {
+99 −20
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.os.Looper;
import android.util.ArraySet;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hearingaid.HearingAidService;
@@ -236,6 +237,17 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when A2DP is connected. Does the following:
     * 1. Check if a hearing aid device is active. We will always prefer hearing aid devices, so if
     * one is active, we will not make this A2DP device active.
     * 2. If there is no hearing aid device active, we will make this A2DP device active.
     * 3. We will make this device active for HFP if it's already connected to HFP
     * 4. If dual mode is disabled, we clear the LE Audio active device to ensure mutual exclusion
     * between classic and LE audio.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleA2dpConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -252,7 +264,9 @@ class ActiveDeviceManager {
                if (mHfpConnectedDevices.contains(device)) {
                    setA2dpActiveDevice(device);
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                    return;
                }
                DatabaseManager dbManager = mAdapterService.getDatabase();
@@ -260,12 +274,25 @@ class ActiveDeviceManager {
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                    setA2dpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                }
            }
        }
    }

    /**
     * Handles the active device logic for when HFP is connected. Does the following:
     * 1. Check if a hearing aid device is active. We will always prefer hearing aid devices, so if
     * one is active, we will not make this HFP device active.
     * 2. If there is no hearing aid device active, we will make this HFP device active.
     * 3. We will make this device active for A2DP if it's already connected to A2DP
     * 4. If dual mode is disabled, we clear the LE Audio active device to ensure mutual exclusion
     * between classic and LE audio.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleHfpConnected(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
@@ -281,7 +308,9 @@ class ActiveDeviceManager {
                if (mA2dpConnectedDevices.contains(device)) {
                    setA2dpActiveDevice(device);
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                    return;
                }
                DatabaseManager dbManager = mAdapterService.getDatabase();
@@ -289,11 +318,13 @@ class ActiveDeviceManager {
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                    setHfpActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                }
            }
        }
    }

    private void handleHearingAidConnected(BluetoothDevice device) {
        synchronized (mLock) {
@@ -334,8 +365,10 @@ class ActiveDeviceManager {
                    && mPendingLeHearingAidActiveDevice.isEmpty()) {
                // New connected device: select it as active
                setLeAudioActiveDevice(device);
                if (!Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
            } else if (mPendingLeHearingAidActiveDevice.contains(device)) {
                setLeHearingAidActiveDevice(device);
                setHearingAidActiveDevice(null);
@@ -450,15 +483,32 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when the A2DP active device changes. Does the following:
     * 1. Clear the active hearing aid.
     * 2. If dual mode is enabled and all supported classic audio profiles are enabled, makes this
     * device active for LE Audio. If not, clear the LE Audio active device.
     * 3. Make HFP active for this device if it is already connected to HFP.
     * 4. Stores the new A2DP active device.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleA2dpActiveDeviceChanged(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(TAG, "handleA2dpActiveDeviceChanged: " + device);
            }
            if (device != null && !Objects.equals(mA2dpActiveDevice, device)) {
            if (!Objects.equals(mA2dpActiveDevice, device)) {
                if (device != null) {
                    setHearingAidActiveDevice(null);
                }
                if (Utils.isDualModeAudioEnabled()
                        && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                    setLeAudioActiveDevice(device);
                } else {
                    setLeAudioActiveDevice(null, true);
                }
            }
            if (mHfpConnectedDevices.contains(device)) {
                setHfpActiveDevice(device);
            }
@@ -467,15 +517,32 @@ class ActiveDeviceManager {
        }
    }

    /**
     * Handles the active device logic for when the HFP active device changes. Does the following:
     * 1. Clear the active hearing aid.
     * 2. If dual mode is enabled and all supported classic audio profiles are enabled, makes this
     * device active for LE Audio. If not, clear the LE Audio active device.
     * 3. Make A2DP active for this device if it is already connected to A2DP.
     * 4. Stores the new HFP active device.
     *
     * @param device is the device that was connected to A2DP
     */
    private void handleHfpActiveDeviceChanged(BluetoothDevice device) {
        synchronized (mLock) {
            if (DBG) {
                Log.d(TAG, "handleHfpActiveDeviceChanged: " + device);
            }
            if (device != null && !Objects.equals(mHfpActiveDevice, device)) {
            if (!Objects.equals(mHfpActiveDevice, device)) {
                if (device != null) {
                    setHearingAidActiveDevice(null);
                }
                if (Utils.isDualModeAudioEnabled()
                        && mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                    setLeAudioActiveDevice(device);
                } else {
                    setLeAudioActiveDevice(null, true);
                }
            }
            if (mA2dpConnectedDevices.contains(device)) {
                setA2dpActiveDevice(device);
            }
@@ -519,8 +586,10 @@ class ActiveDeviceManager {
            }
            // Just assign locally the new value
            if (device != null && !Objects.equals(mLeAudioActiveDevice, device)) {
                if (!Utils.isDualModeAudioEnabled()) {
                    setA2dpActiveDevice(null, true);
                    setHfpActiveDevice(null);
                }
                setHearingAidActiveDevice(null);
            }

@@ -849,16 +918,22 @@ class ActiveDeviceManager {
                    setA2dpActiveDevice(device);
                    if (headsetFallbackDevice != null) {
                        setHfpActiveDevice(device);
                        /* If dual mode is enabled, LEA will be made active once all supported
                        classic audio profiles are made active for the device. */
                        if (!Utils.isDualModeAudioEnabled()) {
                            setLeAudioActiveDevice(null, true);
                        }
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "set LE audio device active: " + device);
                    }
                    setLeAudioActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setA2dpActiveDevice(null, true);
                        setHfpActiveDevice(null);
                    }
                }
            } else {
                if (Objects.equals(headsetFallbackDevice, device)) {
                    if (DBG) {
@@ -867,17 +942,21 @@ class ActiveDeviceManager {
                    setHfpActiveDevice(device);
                    if (a2dpFallbackDevice != null) {
                        setA2dpActiveDevice(a2dpFallbackDevice);
                        if (!Utils.isDualModeAudioEnabled()) {
                            setLeAudioActiveDevice(null, true);
                        }
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "set LE audio device active: " + device);
                    }
                    setLeAudioActiveDevice(device);
                    if (!Utils.isDualModeAudioEnabled()) {
                        setA2dpActiveDevice(null, true);
                        setHfpActiveDevice(null);
                    }
                }
            }
            return true;
        }

+31 −1
Original line number Diff line number Diff line
@@ -1284,8 +1284,9 @@ public class AdapterService extends Service {
     * @param profile           is the profile we are checking for support
     * @return true if the profile is supported by both the local and remote device, false otherwise
     */
    @VisibleForTesting
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    private boolean isProfileSupported(BluetoothDevice device, int profile) {
    boolean isProfileSupported(BluetoothDevice device, int profile) {
        ParcelUuid[] remoteDeviceUuids = getRemoteUuids(device);
        ParcelUuid[] localDeviceUuids = mAdapterProperties.getUuids();
        if (remoteDeviceUuids == null || remoteDeviceUuids.length == 0) {
@@ -5346,6 +5347,35 @@ public class AdapterService extends Service {
        return true;
    }

    /**
     * Checks if all supported classic audio profiles are active on this LE Audio device.
     * @param leAudioDevice the remote device
     * @return {@code true} if all supported classic audio profiles are active on this device,
     * {@code false} otherwise
     */
    public boolean isAllSupportedClassicAudioProfilesActive(BluetoothDevice leAudioDevice) {
        if (mLeAudioService == null) {
            return false;
        }
        boolean a2dpSupported = isProfileSupported(leAudioDevice, BluetoothProfile.A2DP);
        boolean hfpSupported = isProfileSupported(leAudioDevice, BluetoothProfile.HEADSET);

        List<BluetoothDevice> groupDevices = mLeAudioService.getGroupDevices(leAudioDevice);
        if (hfpSupported && mHeadsetService != null) {
            BluetoothDevice activeHfpDevice = mHeadsetService.getActiveDevice();
            if (activeHfpDevice == null || !groupDevices.contains(activeHfpDevice)) {
                return false;
            }
        }
        if (a2dpSupported && mA2dpService != null) {
            BluetoothDevice activeA2dpDevice = mA2dpService.getActiveDevice();
            if (activeA2dpDevice == null || !groupDevices.contains(activeA2dpDevice)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Get the active devices for the BluetoothProfile specified
     *
+39 −1
Original line number Diff line number Diff line
@@ -748,7 +748,34 @@ public class LeAudioService extends ProfileService {
        return result;
    }

    private Integer getActiveGroupId() {
    /**
     * Get all the devices within a given group.
     * @param device the device for which we want to get all devices in its group
     * @return all devices within a given group or empty list
     */
    public List<BluetoothDevice> getGroupDevices(BluetoothDevice device) {
        List<BluetoothDevice> result = new ArrayList<>();
        int groupId = getGroupId(device);

        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
            return result;
        }

        synchronized (mGroupLock) {
            for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
                    : mDeviceDescriptors.entrySet()) {
                if (entry.getValue().mGroupId == groupId) {
                    result.add(entry.getKey());
                }
            }
        }
        return result;
    }

    /**
     * Get the active device group id
     */
    public Integer getActiveGroupId() {
        synchronized (mGroupLock) {
            for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
                LeAudioGroupDescriptor descriptor = entry.getValue();
@@ -1299,6 +1326,9 @@ public class LeAudioService extends ProfileService {
     * @return true on success, otherwise false
     */
    public boolean setActiveDevice(BluetoothDevice device) {
        Log.i(TAG, "setActiveDevice: device=" + device + ", current out="
                + mActiveAudioOutDevice + ", current in=" + mActiveAudioInDevice);
        /* Clear active group */
        if (device == null) {
            Log.e(TAG, "device should not be null!");
            return removeActiveDevice(false);
@@ -1308,6 +1338,14 @@ public class LeAudioService extends ProfileService {
                    + "connected");
            return false;
        }

        if (Utils.isDualModeAudioEnabled()) {
            if (!mAdapterService.isAllSupportedClassicAudioProfilesActive(device)) {
                Log.e(TAG, "setActiveDevice(" + device + "): failed because the device is not "
                                + "active for all supported classic audio profiles");
                return false;
            }
        }
        setActiveGroupWithDevice(device, false);
        return true;
    }
+75 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -43,6 +44,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.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hearingaid.HearingAidService;
@@ -75,12 +77,14 @@ public class ActiveDeviceManagerTest {
    private BluetoothDevice mLeAudioDevice;
    private BluetoothDevice mLeHearingAidDevice;
    private BluetoothDevice mSecondaryAudioDevice;
    private BluetoothDevice mDualModeAudioDevice;
    private ArrayList<BluetoothDevice> mDeviceConnectionStack;
    private BluetoothDevice mMostRecentDevice;
    private ActiveDeviceManager mActiveDeviceManager;
    private long mHearingAidHiSyncId = 1010;

    private static final int TIMEOUT_MS = 1000;
    private boolean mOriginalDualModeAudioState;

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -119,8 +123,10 @@ public class ActiveDeviceManagerTest {
        mLeAudioDevice = TestUtils.getTestDevice(mAdapter, 4);
        mLeHearingAidDevice = TestUtils.getTestDevice(mAdapter, 5);
        mSecondaryAudioDevice = TestUtils.getTestDevice(mAdapter, 6);
        mDualModeAudioDevice = TestUtils.getTestDevice(mAdapter, 7);
        mDeviceConnectionStack = new ArrayList<>();
        mMostRecentDevice = null;
        mOriginalDualModeAudioState = Utils.isDualModeAudioEnabled();

        when(mA2dpService.setActiveDevice(any())).thenReturn(true);
        when(mHeadsetService.getHfpCallAudioPolicy(any())).thenReturn(
@@ -172,6 +178,7 @@ public class ActiveDeviceManagerTest {
    public void tearDown() throws Exception {
        mActiveDeviceManager.cleanup();
        TestUtils.clearAdapterService(mAdapterService);
        Utils.setDualModeAudioStateForTesting(mOriginalDualModeAudioState);
    }

    @Test
@@ -693,6 +700,74 @@ public class ActiveDeviceManagerTest {
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
    }

    /**
     * Verifies that we mutually exclude classic audio profiles (A2DP & HFP) and LE Audio when the
     * dual mode feature is disabled.
     */
    @Test
    public void dualModeAudioDeviceConnected_withDualModeFeatureDisabled() {
        // Turn off the dual mode audio flag
        Utils.setDualModeAudioStateForTesting(false);

        // Ensure we remove the LEA active device when classic audio profiles are made active
        a2dpConnected(mDualModeAudioDevice, true);
        headsetConnected(mDualModeAudioDevice, true);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mDualModeAudioDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mDualModeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS)).removeActiveDevice(true);
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getA2dpActiveDevice());
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getHfpActiveDevice());

        // Ensure we make classic audio profiles inactive when LEA is made active
        leAudioConnected(mDualModeAudioDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mDualModeAudioDevice);
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice());
    }

    /**
     * Verifies that we connect and make active both classic audio profiles (A2DP & HFP) and LE
     * Audio when the dual mode feature is enabled.
     */
    @Test
    public void dualModeAudioDeviceConnected_withDualModeFeatureEnabled() {
        // Turn on the dual mode audio flag
        Utils.setDualModeAudioStateForTesting(true);
        reset(mLeAudioService);
        when(mAdapterService.isAllSupportedClassicAudioProfilesActive(mDualModeAudioDevice))
                .thenReturn(false);

        leAudioConnected(mDualModeAudioDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        // Verify setting LEA active fails when all supported classic audio profiles are not active
        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mDualModeAudioDevice);
        Assert.assertNull(mActiveDeviceManager.getLeAudioActiveDevice());
        Assert.assertNull(mActiveDeviceManager.getA2dpActiveDevice());
        Assert.assertNull(mActiveDeviceManager.getHfpActiveDevice());

        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.removeActiveDevice(anyBoolean())).thenReturn(true);

        // Ensure we make LEA active after all supported classic profiles are active
        a2dpActiveDeviceChanged(mDualModeAudioDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        when(mAdapterService.isAllSupportedClassicAudioProfilesActive(mDualModeAudioDevice))
                .thenReturn(true);
        headsetActiveDeviceChanged(mDualModeAudioDevice);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        verify(mLeAudioService, times(2)).setActiveDevice(mDualModeAudioDevice);
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getA2dpActiveDevice());
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getHfpActiveDevice());
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getLeAudioActiveDevice());

        // Verify LEA made inactive when a supported classic audio profile is made inactive
        a2dpActiveDeviceChanged(null);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        Assert.assertEquals(null, mActiveDeviceManager.getA2dpActiveDevice());
        Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice());
    }

    /**
     * A wired audio device is connected. Then all active devices are set to null.
     */
Loading