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

Commit 1bfda209 authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Revisit audio routing policy when audio mode changed

When audio mode changed, if the active device for the audio mode
supports both A2DP and HFP, make the device be the active device
for both A2DP and HFP.

For example, let HS1 supports A2DP and HFP, and HS2 supports HFP
only. 1) HS1 connected and activated both A2DP and HFP, 2) HS2
connected and explitly activated by user in call, 3) the call ended,
then HS1 should be activated for both A2DP and HFP.

Bug: 299023147
Bug: 300174072
Test: atest BluetoothInstrumentationTests
Change-Id: Iac42fcb08d89aa8b77a8f0d7a314bd3aff87b566
parent aeaae719
Loading
Loading
Loading
Loading
+42 −2
Original line number Diff line number Diff line
@@ -166,6 +166,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        mp.threadStart(mHandlerThread);
        mHandler = new AudioRoutingHandler(mp.handlerThreadGetLooper(mHandlerThread));

        mAudioManager.addOnModeChangedListener(cmd -> mHandler.post(cmd), mHandler);
        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
        mAdapterService.registerBluetoothStateCallback((command) -> mHandler.post(command), this);
    }
@@ -176,6 +177,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
            Log.d(TAG, "cleanup()");
        }

        mAudioManager.removeOnModeChangedListener(mHandler);
        mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);
        mAdapterService.unregisterBluetoothStateCallback(this);
        if (mHandlerThread != null) {
@@ -250,13 +252,16 @@ public class AudioRoutingManager extends ActiveDeviceManager {
        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {}
    }

    private class AudioRoutingHandler extends Handler {
    private class AudioRoutingHandler extends Handler
            implements AudioManager.OnModeChangedListener {
        private final ArrayMap<BluetoothDevice, AudioRoutingDevice> mConnectedDevices =
                new ArrayMap<>();
        private final SparseArray<List<BluetoothDevice>> mActiveDevices = new SparseArray<>();
        private int mAudioMode;

        AudioRoutingHandler(Looper looper) {
            super(looper);
            mAudioMode = mAudioManager.getMode();
        }

        public void handleProfileConnected(int profile, BluetoothDevice device) {
@@ -331,6 +336,41 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                            + device);
        }

        @Override
        public void onModeChanged(int mode) {
            if (DBG) {
                Log.d(TAG, "onModeChanged: " + mAudioMode + " -> " + mode);
            }
            List<BluetoothDevice> a2dpActiveDevices = getActiveDevices(BluetoothProfile.A2DP);
            List<BluetoothDevice> hfpActiveDevices = getActiveDevices(BluetoothProfile.HEADSET);
            if (mode == AudioManager.MODE_NORMAL) {
                for (BluetoothDevice d : a2dpActiveDevices) {
                    // When an A2DP device that also support HFP as well is in use,
                    // be sure that HFP is also activated.
                    if (!hfpActiveDevices.contains(d)
                            && getAudioRoutingDevice(d)
                                    .connectedProfiles
                                    .contains(BluetoothProfile.HEADSET)) {
                        setActiveDevice(BluetoothProfile.HEADSET, d);
                        break;
                    }
                }
            } else if (mode == AudioManager.MODE_IN_CALL) {
                for (BluetoothDevice d : hfpActiveDevices) {
                    // When a HFP device that also support A2DP as well is in use,
                    // be sure that A2DP is also activated.
                    if (!a2dpActiveDevices.contains(d)
                            && getAudioRoutingDevice(d)
                                    .connectedProfiles
                                    .contains(BluetoothProfile.A2DP)) {
                        setActiveDevice(BluetoothProfile.A2DP, d);
                        break;
                    }
                }
            }
            mAudioMode = mode;
        }

        private Optional<BluetoothDevice> getFallbackDevice(
                Collection<AudioRoutingDevice> candidates) {
            List<BluetoothDevice> activatableDevices = new ArrayList<>();
@@ -679,7 +719,7 @@ public class AudioRoutingManager extends ActiveDeviceManager {
                if (!getActiveDevices(p).isEmpty()) {
                    BluetoothMethodProxy mp = BluetoothMethodProxy.getInstance();
                    if (!mp.mediaSessionManagerGetActiveSessions(mSessionManager).isEmpty()
                            || mAudioManager.getMode() == AudioManager.MODE_IN_CALL) {
                            || mAudioMode == AudioManager.MODE_IN_CALL) {
                        Log.i(
                                TAG,
                                "Do not activate the connected device when another device is in"
+51 −23
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -90,6 +91,8 @@ public class AudioRoutingManagerTest {
    private boolean mOriginalDualModeAudioState;
    private TestDatabaseManager mDatabaseManager;
    private TestLooper mTestLooper;
    private ArgumentCaptor<AudioManager.OnModeChangedListener> mModeChangedListenerArgument =
            ArgumentCaptor.forClass(AudioManager.OnModeChangedListener.class);

    @Mock private AdapterService mAdapterService;
    @Mock private ServiceFactory mServiceFactory;
@@ -178,8 +181,6 @@ public class AudioRoutingManagerTest {

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

        a2dpConnected(mA2dpHeadsetDevice, true);
        mTestLooper.dispatchAll();
        verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
@@ -192,8 +193,6 @@ public class AudioRoutingManagerTest {

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

        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        mTestLooper.dispatchAll();
@@ -324,8 +323,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void headsetSecondDeviceDisconnected_fallbackToPhone() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mHeadsetDevice, false);
        switchHeadsetActiveDevice(mHeadsetDevice);
        mTestLooper.dispatchAll();
@@ -344,8 +341,6 @@ public class AudioRoutingManagerTest {

    @Test
    public void headsetSecondDeviceDisconnected_fallbackDeviceActiveWhileRinging() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);

        headsetConnected(mA2dpHeadsetDevice, true);
        a2dpConnected(mA2dpHeadsetDevice, true);
        mTestLooper.dispatchAll();
@@ -365,7 +360,6 @@ public class AudioRoutingManagerTest {

    @Test
    public void a2dpConnectedButHeadsetNotConnected_setA2dpActiveShouldNotBeCalled() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
        a2dpConnected(mA2dpHeadsetDevice, true);

        mTestLooper.moveTimeForward(AudioRoutingManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS / 2);
@@ -376,7 +370,6 @@ public class AudioRoutingManagerTest {

    @Test
    public void headsetConnectedButA2dpNotConnected_setHeadsetActiveShouldNotBeCalled() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        headsetConnected(mA2dpHeadsetDevice, true);

        mTestLooper.moveTimeForward(AudioRoutingManager.A2DP_HFP_SYNC_CONNECTION_TIMEOUT_MS / 2);
@@ -746,8 +739,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void leAudioAndA2dpActivatedThenA2dpDisconnected_fallbackToLeAudio() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -770,8 +761,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void leAudioSetConnectedThenNotActiveOneDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -795,8 +784,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_noFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -822,8 +809,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void leAudioSetConnectedThenActiveOneDisconnected_hasFallback() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        leAudioConnected(mLeAudioDevice);
        mTestLooper.dispatchAll();
        verify(mLeAudioService).setActiveDevice(mLeAudioDevice);
@@ -844,8 +829,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void a2dpAndLeAudioConnectedThenLeAudioDisconnected_fallbackToPhone() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        a2dpConnected(mA2dpDevice, false);
        switchA2dpActiveDevice(mA2dpDevice);
        mTestLooper.dispatchAll();
@@ -868,8 +851,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void a2dpHeadsetAndLeAudioConnectedThenLeAudioDisconnected_fallbackToA2dpHeadset() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

        a2dpConnected(mHeadsetDevice, true);
        headsetConnected(mHeadsetDevice, true);
        mTestLooper.dispatchAll();
@@ -921,7 +902,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void activeDeviceChange_withHearingAidLeAudioAndA2dpDevices() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
        when(mHearingAidService.removeActiveDevice(anyBoolean())).thenReturn(true);

        hearingAidConnected(mHearingAidDevice);
@@ -1101,6 +1081,54 @@ public class AudioRoutingManagerTest {
        verify(mHearingAidService).removeActiveDevice(false);
    }

    /**
     * A2dpHeadset connected then HFP only is activated while in call. When the call ended,
     * A2dpHeadset should be activated for HFP.
     */
    @Test
    public void setA2dpHeadsetActiveWhenModeChangedToNormal() {
        verify(mAudioManager)
                .addOnModeChangedListener(any(), mModeChangedListenerArgument.capture());

        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        mModeChangedListenerArgument.getValue().onModeChanged(AudioManager.MODE_IN_CALL);
        headsetConnected(mHeadsetDevice, false);
        switchHeadsetActiveDevice(mHeadsetDevice);
        mTestLooper.dispatchAll();

        verify(mHeadsetService).setActiveDevice(mHeadsetDevice);

        Mockito.clearInvocations(mHeadsetService);
        mModeChangedListenerArgument.getValue().onModeChanged(AudioManager.MODE_NORMAL);
        mTestLooper.dispatchAll();
        verify(mHeadsetService).setActiveDevice(mA2dpHeadsetDevice);
    }

    /**
     * A2dpHeadset connected then A2DP only is activated. When a call starts via A2dpHeadset,
     * A2dpHeadset should be activated for HFP.
     */
    @Test
    public void setA2dpHeadsetActiveWhenModeChangedToInCall() {
        verify(mAudioManager)
                .addOnModeChangedListener(any(), mModeChangedListenerArgument.capture());

        a2dpConnected(mA2dpHeadsetDevice, true);
        headsetConnected(mA2dpHeadsetDevice, true);
        mModeChangedListenerArgument.getValue().onModeChanged(AudioManager.MODE_NORMAL);
        a2dpConnected(mA2dpDevice, false);
        switchA2dpActiveDevice(mA2dpDevice);
        mTestLooper.dispatchAll();

        verify(mA2dpService).setActiveDevice(mA2dpDevice);

        Mockito.clearInvocations(mA2dpService);
        mModeChangedListenerArgument.getValue().onModeChanged(AudioManager.MODE_IN_CALL);
        mTestLooper.dispatchAll();
        verify(mA2dpService).setActiveDevice(mA2dpHeadsetDevice);
    }

    /** Helper to indicate A2dp connected for a device. */
    private void a2dpConnected(BluetoothDevice device, boolean supportHfp) {
        mDatabaseManager.setProfileConnectionPolicy(