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

Commit d70a572a authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Be sure A2DP/HFP profile activated when connected

When a BT device connected that support both A2DP and HFP,
the activation of first connected profile will be delayed until
the other profile connected. If the other profile failed to be
connected, the first connected profile is never activated.
This CL fixes these cases and also activate the first connected
profile earlier based on the audio mode.

Bug: 274741724
Test: atest BluetoothInstrumentationTests:ActiveDeviceManagerTest
Change-Id: Ic462aba67d81e69846f2e829df1b9eae997b54ea
parent 87d96ef5
Loading
Loading
Loading
Loading
+52 −5
Original line number Diff line number Diff line
@@ -116,7 +116,9 @@ import java.util.Set;
 */
class ActiveDeviceManager {
    private static final String TAG = "ActiveDeviceManager";
    private static final boolean DBG = true; // Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean DBG = true;
    @VisibleForTesting
    static final int A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS = 5_000;

    private final AdapterService mAdapterService;
    private final ServiceFactory mFactory;
@@ -148,6 +150,8 @@ class ActiveDeviceManager {
    private BluetoothDevice mLeAudioActiveDevice = null;
    @GuardedBy("mLock")
    private BluetoothDevice mLeHearingAidActiveDevice = null;
    @GuardedBy("mLock")
    private BluetoothDevice mPendingActiveDevice = null;

    // Broadcast receiver for all changes
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -270,14 +274,31 @@ class ActiveDeviceManager {
                    }
                    return;
                }
                // Activate A2DP if audio mode is normal or HFP is not supported or enabled.
                DatabaseManager dbManager = mAdapterService.getDatabase();
                // Activate A2DP, if HFP is not supported or enabled.
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED
                        || mAudioManager.getMode() == AudioManager.MODE_NORMAL) {
                    boolean a2dpMadeActive = setA2dpActiveDevice(device);
                    if (a2dpMadeActive && !Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "A2DP activation is suspended until HFP connected: "
                                + device);
                    }

                    mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    mPendingActiveDevice = device;
                    // Activate A2DP if HFP is failed to connect.
                    mHandler.postDelayed(
                            () -> {
                                Log.w(TAG, "HFP connection timeout. Activate A2DP for " + device);
                                setA2dpActiveDevice(device);
                            },
                            mPendingActiveDevice,
                            A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS);
                }
            }
        }
@@ -317,10 +338,11 @@ class ActiveDeviceManager {
                    }
                    return;
                }
                // Activate HFP if audio mode is not normal or A2DP is not supported or enabled.
                DatabaseManager dbManager = mAdapterService.getDatabase();
                // Activate HFP, if A2DP is not supported or enabled.
                if (dbManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP)
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                        != BluetoothProfile.CONNECTION_POLICY_ALLOWED
                        || mAudioManager.getMode() != AudioManager.MODE_NORMAL) {
                    if (isWatch(device)) {
                        Log.i(TAG, "Do not set hfp active for watch device " + device);
                        return;
@@ -332,6 +354,21 @@ class ActiveDeviceManager {
                    if (hfpMadeActive && !Utils.isDualModeAudioEnabled()) {
                        setLeAudioActiveDevice(null, true);
                    }
                } else {
                    if (DBG) {
                        Log.d(TAG, "HFP activation is suspended until A2DP connected: "
                                + device);
                    }
                    mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                    mPendingActiveDevice = device;
                    // Activate HFP if A2DP is failed to connect.
                    mHandler.postDelayed(
                            () -> {
                                Log.w(TAG, "A2DP connection timeout. Activate HFP for " + device);
                                setHfpActiveDevice(device);
                            },
                            mPendingActiveDevice,
                            A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS);
                }
            }
        }
@@ -726,6 +763,12 @@ class ActiveDeviceManager {
            Log.d(TAG, "setA2dpActiveDevice(" + device + ")"
                    + (device == null ? " hasFallbackDevice=" + hasFallbackDevice : ""));
        }
        synchronized (mLock) {
            if (mPendingActiveDevice != null) {
                mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                mPendingActiveDevice = null;
            }
        }

        final A2dpService a2dpService = mFactory.getA2dpService();
        if (a2dpService == null) {
@@ -755,6 +798,10 @@ class ActiveDeviceManager {
            if (DBG) {
                Log.d(TAG, "setHfpActiveDevice(" + device + ")");
            }
            if (mPendingActiveDevice != null) {
                mHandler.removeCallbacksAndMessages(mPendingActiveDevice);
                mPendingActiveDevice = null;
            }
            final HeadsetService headsetService = mFactory.getHeadsetService();
            if (headsetService == null) {
                return false;
+54 −14
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.bluetooth.btservice;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -83,7 +85,9 @@ public class ActiveDeviceManagerTest {
    private ActiveDeviceManager mActiveDeviceManager;
    private long mHearingAidHiSyncId = 1010;

    private static final int TIMEOUT_MS = 1000;
    private static final int TIMEOUT_MS = 1_000;
    private static final int A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS =
            ActiveDeviceManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS + 2_000;
    private boolean mOriginalDualModeAudioState;

    @Mock private AdapterService mAdapterService;
@@ -193,6 +197,17 @@ public class ActiveDeviceManagerTest {
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
    }

    @Test
    public void a2dpHeadsetConnected_setA2dpActiveShouldBeCalledAfterHeadsetConnected() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        a2dpConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, after(TIMEOUT_MS).never()).setActiveDevice(mA2dpHeadsetDevice);
        headsetConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
    }

    /**
     * Two A2DP are connected. Should set the second one active.
     */
@@ -322,6 +337,25 @@ public class ActiveDeviceManagerTest {
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
    }

    @Test
    public void a2dpConnectedButHeadsetNotConnected_setA2dpActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        a2dpConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, timeout(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS))
                .setActiveDevice(mA2dpHeadsetDevice);
    }


    @Test
    public void headsetConnectedButA2dpNotConnected_setHeadsetActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        headsetConnected(mA2dpHeadsetDevice, true);
        verify(mHeadsetService, timeout(A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS))
                .setActiveDevice(mA2dpHeadsetDevice);
    }

    /**
     * A headset device with connecting audio policy set to NOT ALLOWED.
     */
@@ -366,8 +400,9 @@ public class ActiveDeviceManagerTest {
    public void hearingAidActive_clearA2dpAndHeadsetActive() {
        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS).atLeastOnce()).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS).atLeastOnce())
                .setActiveDevice(mA2dpHeadsetDevice);

        hearingAidActiveDeviceChanged(mHearingAidDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
@@ -502,8 +537,9 @@ public class ActiveDeviceManagerTest {
    public void leAudioActive_clearA2dpAndHeadsetActive() {
        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS).atLeastOnce()).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS).atLeastOnce())
                .setActiveDevice(mA2dpHeadsetDevice);

        leAudioActiveDeviceChanged(mLeAudioDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).removeActiveDevice(false);
@@ -514,14 +550,14 @@ public class ActiveDeviceManagerTest {
     * An LE Audio is connected. Then a combo (A2DP + Headset) device is connected.
     */
    @Test
    public void leAudioActive_dontSetA2dpAndHeadsetActive() {
    public void leAudioActive_setA2dpAndHeadsetActive() {
        leAudioActiveDeviceChanged(mLeAudioDevice);
        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);

        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
        verify(mA2dpService, atLeastOnce()).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, atLeastOnce()).setActiveDevice(mA2dpHeadsetDevice);
    }

    /**
@@ -647,7 +683,8 @@ public class ActiveDeviceManagerTest {

        a2dpDisconnected(mA2dpDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).removeActiveDevice(false);
        verify(mHearingAidService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mHearingAidDevice);
        verify(mHearingAidService, timeout(TIMEOUT_MS).times(2))
                .setActiveDevice(mHearingAidDevice);
    }

    /**
@@ -721,9 +758,11 @@ public class ActiveDeviceManagerTest {
        // 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);
        verify(mA2dpService, timeout(TIMEOUT_MS).atLeastOnce())
                .setActiveDevice(mDualModeAudioDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS).atLeastOnce())
                .setActiveDevice(mDualModeAudioDevice);
        verify(mLeAudioService, timeout(TIMEOUT_MS).atLeastOnce()).removeActiveDevice(true);
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getA2dpActiveDevice());
        Assert.assertEquals(mDualModeAudioDevice, mActiveDeviceManager.getHfpActiveDevice());

@@ -807,8 +846,9 @@ public class ActiveDeviceManagerTest {
        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS).atLeastOnce()).setActiveDevice(mA2dpHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS).atLeastOnce())
                .setActiveDevice(mA2dpHeadsetDevice);
        verify(mLeAudioService, never()).removeActiveDevice(anyBoolean());

        headsetConnected(mHeadsetDevice, false);