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

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

Lazy activation of A2DP or HFP

If a BT classic that supports both A2DP and HFP is connected
when LE audio is active and music is playing, we should be
careful on deactivating of LE audio otherwise audio could come
from phone speaker.

This CL fixes following audio leak from phone speaker by pending
the activation of HFP and deactivation of LE audio till A2DP is
available.

Before the CL:
1. HFP is activated
2. LE audio is deactivated
3. Music comes from DUT (Audio leak)
4. A2DP is activated
5. Music comes from A2DP

After the CL:
1. HFP is connected, but not activated if HFP is not in use
2. A2DP is connected, activated if A2DP is in use
3. HFP is activated if its activation was pending
4. LE audio is deactivated.

Bug: 247453067
Tag: #refactor
Test: atest BluetoothInstrumentationTests:ActiveDeviceManager
Change-Id: Iee2442b3cc6aeecce4a0ec1e87f1b477c894199b
parent 6d42d9c5
Loading
Loading
Loading
Loading
+115 −15
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;

/**
 * The active device manager is responsible for keeping track of the
@@ -130,15 +131,18 @@ class ActiveDeviceManager {
    private Handler mHandler = null;
    private final AudioManager mAudioManager;
    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback;
    private final AudioManagerOnModeChangedListener mAudioManagerOnModeChangedListener;

    private final List<BluetoothDevice> mA2dpConnectedDevices = new ArrayList<>();
    private final List<BluetoothDevice> mHfpConnectedDevices = new ArrayList<>();
    private final List<BluetoothDevice> mHearingAidConnectedDevices = new ArrayList<>();
    private final List<BluetoothDevice> mLeAudioConnectedDevices = new ArrayList<>();
    private final List<BluetoothDevice> mLeHearingAidConnectedDevices = new ArrayList<>();
    private List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>();
    private final List<BluetoothDevice> mPendingLeHearingAidActiveDevice = new ArrayList<>();
    private BluetoothDevice mA2dpActiveDevice = null;
    private BluetoothDevice mPendingA2dpActiveDevice = null;
    private BluetoothDevice mHfpActiveDevice = null;
    private BluetoothDevice mPendingHfpActiveDevice = null;
    private BluetoothDevice mHearingAidActiveDevice = null;
    private BluetoothDevice mLeAudioActiveDevice = null;
    private BluetoothDevice mLeHearingAidActiveDevice = null;
@@ -245,16 +249,43 @@ class ActiveDeviceManager {
                        if (mA2dpConnectedDevices.contains(device)) {
                            break;      // The device is already connected
                        }
                        // New connected A2DP device
                        mA2dpConnectedDevices.add(device);
                        if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
                            // New connected device: select it as active
                            // Lazy active A2DP if it is not being used.
                            // This will prevent the deactivation of LE audio
                            // earlier than the activation of HFP.
                            switch (mAudioManager.getMode()) {
                                case AudioManager.MODE_NORMAL:
                                    break;
                                case AudioManager.MODE_RINGTONE: {
                                    HeadsetService headsetService = mFactory.getHeadsetService();
                                    if (mHfpActiveDevice == null && headsetService != null
                                            && headsetService.isInbandRingingEnabled()) {
                                        mPendingA2dpActiveDevice = device;
                                    }
                                    break;
                                }
                                case AudioManager.MODE_IN_CALL:
                                case AudioManager.MODE_IN_COMMUNICATION:
                                case AudioManager.MODE_CALL_SCREENING:
                                case AudioManager.MODE_CALL_REDIRECT:
                                case AudioManager.MODE_COMMUNICATION_REDIRECT: {
                                    if (mHfpActiveDevice == null) {
                                        mPendingA2dpActiveDevice = device;
                                    }
                                }
                            }
                            if (mPendingA2dpActiveDevice == null) {
                                // select the device as active if not lazy active
                                setA2dpActiveDevice(device);
                                setLeAudioActiveDevice(null);
                            }
                        }
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        // A2DP device disconnected
                        if (DBG) {
                            Log.d(TAG,
                                    "handleMessage(MESSAGE_A2DP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -308,16 +339,37 @@ class ActiveDeviceManager {
                        if (mHfpConnectedDevices.contains(device)) {
                            break;      // The device is already connected
                        }
                        // New connected HFP device.
                        mHfpConnectedDevices.add(device);
                        if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null) {
                            // New connected device: select it as active
                            // Lazy active HFP if it is not being used.
                            // This will prevent the deactivation of LE audio
                            // earlier than the activation of A2DP.
                            switch (mAudioManager.getMode()) {
                                case AudioManager.MODE_NORMAL:
                                    if (mA2dpActiveDevice == null) {
                                        mPendingHfpActiveDevice = device;
                                    }
                                    break;
                                case AudioManager.MODE_RINGTONE: {
                                    HeadsetService headsetService = mFactory.getHeadsetService();
                                    if (headsetService == null
                                            || !headsetService.isInbandRingingEnabled()) {
                                        mPendingHfpActiveDevice = device;
                                    }
                                    break;
                                }
                            }
                            if (mPendingHfpActiveDevice == null) {
                                // select the device as active if not lazy active
                                setHfpActiveDevice(device);
                                setLeAudioActiveDevice(null);
                            }
                        }
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        // HFP device disconnected
                        if (DBG) {
                            Log.d(TAG,
                                    "handleMessage(MESSAGE_HFP_ACTION_CONNECTION_STATE_CHANGED): "
@@ -371,7 +423,7 @@ class ActiveDeviceManager {
                            break;      // The device is already connected
                        }
                        mHearingAidConnectedDevices.add(device);
                        // New connected device: select it as active
                        // New connected hearing aid device: select it as active
                        setHearingAidActiveDevice(device);
                        setA2dpActiveDevice(null);
                        setHfpActiveDevice(null);
@@ -379,7 +431,7 @@ class ActiveDeviceManager {
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        // Hearing aid device disconnected
                        if (DBG) {
                            Log.d(TAG, "handleMessage(MESSAGE_HEARING_AID_ACTION_CONNECTION_STATE"
                                    + "_CHANGED): device " + device + " disconnected");
@@ -435,7 +487,7 @@ class ActiveDeviceManager {
                        mLeAudioConnectedDevices.add(device);
                        if (mHearingAidActiveDevice == null && mLeHearingAidActiveDevice == null
                                && mPendingLeHearingAidActiveDevice.isEmpty()) {
                            // New connected device: select it as active
                            // New connected LE audio device: select it as active
                            setLeAudioActiveDevice(device);
                            setA2dpActiveDevice(null);
                            setHfpActiveDevice(null);
@@ -448,7 +500,7 @@ class ActiveDeviceManager {
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        // LE audio device disconnected
                        if (DBG) {
                            Log.d(TAG, "handleMessage(MESSAGE_LE_AUDIO_ACTION_CONNECTION_STATE"
                                    + "_CHANGED): device " + device + " disconnected");
@@ -511,7 +563,7 @@ class ActiveDeviceManager {
                        } else if (Objects.equals(mLeAudioActiveDevice, device)) {
                            mLeHearingAidActiveDevice = device;
                        } else {
                            // New connected device: select it as active
                            // New connected LE hearing aid device: select it as active
                            setLeHearingAidActiveDevice(device);
                            setHearingAidActiveDevice(null);
                            setA2dpActiveDevice(null);
@@ -520,7 +572,7 @@ class ActiveDeviceManager {
                        break;
                    }
                    if (prevState == BluetoothProfile.STATE_CONNECTED) {
                        // Device disconnected
                        // LE hearing aid device disconnected
                        if (DBG) {
                            Log.d(TAG, "handleMessage(MESSAGE_HAP_ACTION_CONNECTION_STATE"
                                    + "_CHANGED): device " + device + " disconnected");
@@ -558,6 +610,40 @@ class ActiveDeviceManager {
        }
    }

    private class AudioManagerOnModeChangedListener implements AudioManager.OnModeChangedListener {
        public void onModeChanged(int mode) {
            switch (mode) {
                case AudioManager.MODE_NORMAL: {
                    if (mPendingA2dpActiveDevice != null) {
                        setA2dpActiveDevice(mPendingA2dpActiveDevice);
                        setLeAudioActiveDevice(null);
                    }
                    break;
                }
                case AudioManager.MODE_RINGTONE: {
                    final HeadsetService headsetService = mFactory.getHeadsetService();
                    if (headsetService != null && headsetService.isInbandRingingEnabled()
                            && mPendingHfpActiveDevice != null) {
                        setHfpActiveDevice(mPendingHfpActiveDevice);
                        setLeAudioActiveDevice(null);
                    }
                    break;
                }
                case AudioManager.MODE_IN_CALL:
                case AudioManager.MODE_IN_COMMUNICATION:
                case AudioManager.MODE_CALL_SCREENING:
                case AudioManager.MODE_CALL_REDIRECT:
                case AudioManager.MODE_COMMUNICATION_REDIRECT:  {
                    if (mPendingHfpActiveDevice != null) {
                        setHfpActiveDevice(mPendingHfpActiveDevice);
                        setLeAudioActiveDevice(null);
                    }
                    break;
                }
            }
        }
    }

    /** Notifications of audio device connection and disconnection events. */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
@@ -604,6 +690,7 @@ class ActiveDeviceManager {
        mFactory = factory;
        mAudioManager = service.getSystemService(AudioManager.class);
        mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback();
        mAudioManagerOnModeChangedListener = new AudioManagerOnModeChangedListener();
    }

    void start() {
@@ -630,6 +717,11 @@ class ActiveDeviceManager {
        mAdapterService.registerReceiver(mReceiver, filter);

        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);
        mAudioManager.addOnModeChangedListener(command -> {
            if (!mHandler.post(command)) {
                throw new RejectedExecutionException(mHandler + " is shutting down");
            }
        }, mAudioManagerOnModeChangedListener);
    }

    void cleanup() {
@@ -672,6 +764,10 @@ class ActiveDeviceManager {
            return;
        }
        mA2dpActiveDevice = device;
        mPendingA2dpActiveDevice = null;
        if (mPendingHfpActiveDevice != null) {
            setHfpActiveDevice(mPendingHfpActiveDevice);
        }
    }

    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -687,6 +783,10 @@ class ActiveDeviceManager {
            return;
        }
        mHfpActiveDevice = device;
        mPendingHfpActiveDevice = null;
        if (mPendingA2dpActiveDevice != null) {
            setA2dpActiveDevice(mPendingA2dpActiveDevice);
        }
    }

    private void setHearingAidActiveDevice(BluetoothDevice device) {
+11 −1
Original line number Diff line number Diff line
@@ -236,6 +236,8 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void onlyHeadsetConnected_setHeadsetActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);
    }
@@ -245,6 +247,8 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void secondHeadsetConnected_setSecondHeadsetActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);

@@ -257,6 +261,8 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void lastHeadsetDisconnected_clearHeadsetActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);

@@ -269,6 +275,8 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void headsetActiveDeviceSelected_setActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mHeadsetDevice);

@@ -288,6 +296,8 @@ public class ActiveDeviceManagerTest {
     */
    @Test
    public void headsetSecondDeviceDisconnected_fallbackDeviceActive() {
        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);

        headsetConnected(mSecondaryAudioDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);

@@ -501,7 +511,7 @@ public class ActiveDeviceManagerTest {
    public void leAudioActive_setHeadsetActiveExplicitly() {
        Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
                LeAudioService.isEnabled());

        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_IN_CALL);
        leAudioActiveDeviceChanged(mLeAudioDevice);
        headsetConnected(mA2dpHeadsetDevice);
        headsetActiveDeviceChanged(mA2dpHeadsetDevice);