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

Commit bfe2ae56 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Revisit audio routing policy when audio mode changed" into main

parents 5592990a 1bfda209
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
@@ -59,6 +59,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
@@ -92,6 +93,8 @@ public class AudioRoutingManagerTest {
    private boolean mOriginalDualModeAudioState;
    private TestDatabaseManager mDatabaseManager;
    private TestLooper mTestLooper;
    private ArgumentCaptor<AudioManager.OnModeChangedListener> mModeChangedListenerArgument =
            ArgumentCaptor.forClass(AudioManager.OnModeChangedListener.class);

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

@@ -180,8 +183,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);
@@ -194,8 +195,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();
@@ -326,8 +325,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void headsetSecondDeviceDisconnected_fallbackToPhone() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

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

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

        headsetConnected(mA2dpHeadsetDevice, true);
        a2dpConnected(mA2dpHeadsetDevice, true);
        mTestLooper.dispatchAll();
@@ -367,7 +362,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);
@@ -378,7 +372,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);
@@ -748,8 +741,6 @@ public class AudioRoutingManagerTest {
     */
    @Test
    public void leAudioAndA2dpActivatedThenA2dpDisconnected_fallbackToLeAudio() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);

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

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

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

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

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

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

        hearingAidConnected(mHearingAidDevice);
@@ -1103,6 +1083,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(