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

Commit bdcb9dc8 authored by Qasim Javed's avatar Qasim Javed Committed by Automerger Merge Worker
Browse files

Merge "Added Bluetooth audio device fallback on disconnect" am: 97914313

parents bb8f8e20 97914313
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -490,6 +490,20 @@ public class A2dpService extends ProfileService {
                if (mActiveDevice == null) return;
                previousActiveDevice = mActiveDevice;
            }

            int prevActiveConnectionState = getConnectionState(previousActiveDevice);

            // As per b/202602952, if we remove the active device due to a disconnection,
            // we need to check if another device is connected and set it active instead.
            // Calling this before any other active related calls has the same effect as
            // a classic active device switch.
            BluetoothDevice fallbackdevice = getFallbackDevice();
            if (fallbackdevice != null && prevActiveConnectionState
                    != BluetoothProfile.STATE_CONNECTED) {
                setActiveDevice(fallbackdevice);
                return;
            }

            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
            updateAndBroadcastActiveDevice(null);
@@ -499,7 +513,7 @@ public class A2dpService extends ProfileService {
            // device, the user has explicitly switched the output to the local device and music
            // should continue playing. Otherwise, the remote device has been indeed disconnected
            // and audio should be suspended before switching the output to the local device.
            boolean stopAudio = forceStopPlayingAudio || (getConnectionState(previousActiveDevice)
            boolean stopAudio = forceStopPlayingAudio || (prevActiveConnectionState
                        != BluetoothProfile.STATE_CONNECTED);
            mAudioManager.handleBluetoothActiveDeviceChanged(null, previousActiveDevice,
                    BluetoothProfileConnectionInfo.createA2dpInfo(!stopAudio, -1));
@@ -1241,6 +1255,16 @@ public class A2dpService extends ProfileService {
        }
    }

    /**
     * Retrieves the most recently connected device in the A2DP connected devices list.
     */
    private BluetoothDevice getFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        return dbManager != null ? dbManager
            .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
            : null;
    }

    /**
     * Binder object: must be a static class or memory leak may occur.
     */
+4 −2
Original line number Diff line number Diff line
@@ -233,7 +233,8 @@ class ActiveDeviceManager {
                                    + "device " + device + " disconnected");
                        }
                        mA2dpConnectedDevices.remove(device);
                        if (Objects.equals(mA2dpActiveDevice, device)) {
                        if (mA2dpConnectedDevices.isEmpty()
                                && Objects.equals(mA2dpActiveDevice, device)) {
                            setA2dpActiveDevice(null);
                        }
                    }
@@ -294,7 +295,8 @@ class ActiveDeviceManager {
                                    + "device " + device + " disconnected");
                        }
                        mHfpConnectedDevices.remove(device);
                        if (Objects.equals(mHfpActiveDevice, device)) {
                        if (mHfpConnectedDevices.isEmpty()
                                && Objects.equals(mHfpActiveDevice, device)) {
                            setHfpActiveDevice(null);
                        }
                    }
+32 −0
Original line number Diff line number Diff line
@@ -639,6 +639,38 @@ public class DatabaseManager {
        return mostRecentlyConnectedDevices;
    }

    /**
     * Gets the most recently connected bluetooth device in a given list.
     *
     * @param devicesList the list of {@link BluetoothDevice} to search in
     * @return the most recently connected {@link BluetoothDevice} in the given
     *         {@code devicesList}, or null if an error occurred
     *
     * @hide
     */
    public BluetoothDevice getMostRecentlyConnectedDevicesInList(
            List<BluetoothDevice> devicesList) {
        if (devicesList == null) {
            return null;
        }

        BluetoothDevice mostRecentDevice = null;
        long mostRecentLastActiveTime = -1;
        synchronized (mMetadataCache) {
            for (BluetoothDevice device : devicesList) {
                String address = device.getAddress();
                Metadata metadata = mMetadataCache.get(address);
                if (metadata != null && (mostRecentLastActiveTime == -1
                            || mostRecentLastActiveTime < metadata.last_active_time)) {
                    mostRecentLastActiveTime = metadata.last_active_time;
                    mostRecentDevice = device;
                }

            }
        }
        return mostRecentDevice;
    }

    /**
     * Gets the last active a2dp device
     *
+20 −0
Original line number Diff line number Diff line
@@ -1313,6 +1313,16 @@ public class HeadsetService extends ProfileService {
     */
    private void removeActiveDevice() {
        synchronized (mStateMachines) {
            // As per b/202602952, if we remove the active device due to a disconnection,
            // we need to check if another device is connected and set it active instead.
            // Calling this before any other active related calls has the same effect as
            // a classic active device switch.
            BluetoothDevice fallbackDevice = getFallbackDevice();
            if (fallbackDevice != null && mActiveDevice != null
                    && getConnectionState(mActiveDevice) != BluetoothProfile.STATE_CONNECTED) {
                setActiveDevice(fallbackDevice);
                return;
            }
            // Clear the active device
            if (mVoiceRecognitionStarted) {
                if (!stopVoiceRecognition(mActiveDevice)) {
@@ -2112,6 +2122,16 @@ public class HeadsetService extends ProfileService {
                == mStateMachinesThread.getId());
    }

    /**
     * Retrieves the most recently connected device in the A2DP connected devices list.
     */
    private BluetoothDevice getFallbackDevice() {
        DatabaseManager dbManager = mAdapterService.getDatabase();
        return dbManager != null ? dbManager
            .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
            : null;
    }

    @Override
    public void dump(StringBuilder sb) {
        boolean isScoOn = mSystemInterface.getAudioManager().isBluetoothScoOn();
+39 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
@@ -58,6 +59,7 @@ public class ActiveDeviceManagerTest {
    private BluetoothDevice mA2dpHeadsetDevice;
    private BluetoothDevice mHearingAidDevice;
    private BluetoothDevice mLeAudioDevice;
    private BluetoothDevice mSecondaryAudioDevice;
    private ActiveDeviceManager mActiveDeviceManager;
    private static final int TIMEOUT_MS = 1000;

@@ -68,6 +70,7 @@ public class ActiveDeviceManagerTest {
    @Mock private HearingAidService mHearingAidService;
    @Mock private LeAudioService mLeAudioService;
    @Mock private AudioManager mAudioManager;
    @Mock private DatabaseManager mDatabaseManager;

    @Before
    public void setUp() throws Exception {
@@ -82,6 +85,7 @@ public class ActiveDeviceManagerTest {
        when(mAdapterService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
        when(mAdapterService.getSystemServiceName(AudioManager.class))
                .thenReturn(Context.AUDIO_SERVICE);
        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
        when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService);
        when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
        when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
@@ -90,6 +94,8 @@ public class ActiveDeviceManagerTest {
        when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
        when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
        when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any()))
                .thenReturn(mSecondaryAudioDevice);

        mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory);
        mActiveDeviceManager.start();
@@ -101,6 +107,7 @@ public class ActiveDeviceManagerTest {
        mA2dpHeadsetDevice = TestUtils.getTestDevice(mAdapter, 2);
        mHearingAidDevice = TestUtils.getTestDevice(mAdapter, 3);
        mLeAudioDevice = TestUtils.getTestDevice(mAdapter, 4);
        mSecondaryAudioDevice = TestUtils.getTestDevice(mAdapter, 4);
    }

    @After
@@ -166,6 +173,22 @@ public class ActiveDeviceManagerTest {
        Assert.assertEquals(mA2dpDevice, mActiveDeviceManager.getA2dpActiveDevice());
    }

    /**
     * Two A2DP devices are connected and the current active is then disconnected.
     * Should then set active device to fallback device.
     */
    @Test
    public void a2dpSecondDeviceDisconnected_fallbackDeviceActive() {
        a2dpConnected(mSecondaryAudioDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);

        a2dpConnected(mA2dpDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);

        a2dpDisconnected(mA2dpDevice);
        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
    }

    /**
     * One Headset is connected.
     */
@@ -218,6 +241,22 @@ public class ActiveDeviceManagerTest {
    }


    /**
     * Two Headsets are connected and the current active is then disconnected.
     * Should then set active device to fallback device.
     */
    @Test
    public void headsetSecondDeviceDisconnected_fallbackDeviceActive() {
        headsetConnected(mSecondaryAudioDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);

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

        headsetDisconnected(mHeadsetDevice);
        verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mSecondaryAudioDevice);
    }

    /**
     * A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
     */