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

Commit 03387266 authored by Rahul Sabnis's avatar Rahul Sabnis
Browse files

Telecomm changes to support dual mode Bluetooth audio devices

Bug: 265077663
Test: Manual
Merged-In: I4d2212178ffced79b812f4e808b8331ae0fe689e
Change-Id: I4d2212178ffced79b812f4e808b8331ae0fe689e
parent efa6ad0e
Loading
Loading
Loading
Loading
+76 −30
Original line number Diff line number Diff line
@@ -28,7 +28,9 @@ import android.content.Context;
import android.media.AudioManager;
import android.media.AudioDeviceInfo;
import android.media.audio.common.AudioDevice;
import android.os.Bundle;
import android.telecom.Log;
import android.util.ArraySet;
import android.util.LocalLog;

import com.android.internal.util.IndentingPrintWriter;
@@ -234,18 +236,38 @@ public class BluetoothDeviceManager {
    }

    public int getNumConnectedDevices() {
        synchronized (mLock) {
            return mHfpDevicesByAddress.size() +
                    mHearingAidDevicesByAddress.size() +
                    getLeAudioConnectedDevices().size();
        }
        return getConnectedDevices().size();
    }

    public Collection<BluetoothDevice> getConnectedDevices() {
        synchronized (mLock) {
            ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
            ArraySet<BluetoothDevice> result = new ArraySet<>();

            // Set storing the group ids of all dual mode audio devices to de-dupe them
            Set<Integer> dualModeGroupIds = new ArraySet<>();
            for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) {
                result.add(hfpDevice);
                if (mBluetoothLeAudioService == null) {
                    continue;
                }
                int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice);
                if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
                    dualModeGroupIds.add(groupId);
                }
            }

            result.addAll(mHearingAidDevicesByAddress.values());
            result.addAll(getLeAudioConnectedDevices());
            if (mBluetoothLeAudioService == null) {
                return Collections.unmodifiableCollection(result);
            }
            for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) {
                // Exclude dual mode audio devices included from the HFP devices list
                int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice);
                if (groupId != BluetoothLeAudio.GROUP_ID_INVALID
                        && !dualModeGroupIds.contains(groupId)) {
                    result.add(leAudioDevice);
                }
            }
            return Collections.unmodifiableCollection(result);
        }
    }
@@ -253,9 +275,9 @@ public class BluetoothDeviceManager {
    // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
    // together by their hiSyncId.
    public Collection<BluetoothDevice> getUniqueConnectedDevices() {
        ArrayList<BluetoothDevice> result;
        ArraySet<BluetoothDevice> result;
        synchronized (mLock) {
            result = new ArrayList<>(mHfpDevicesByAddress.values());
            result = new ArraySet<>(mHfpDevicesByAddress.values());
        }
        Set<Long> seenHiSyncIds = new LinkedHashSet<>();
        // Add the left-most active device to the seen list so that we match up with the list
@@ -367,6 +389,8 @@ public class BluetoothDeviceManager {
                return;
            }
            if (!targetDeviceMap.containsKey(device.getAddress())) {
                Log.i(this, "Adding device with address: " + device + " and devicetype="
                        + getDeviceTypeString(deviceType));
                targetDeviceMap.put(device.getAddress(), device);
                mBluetoothRouteManager.onDeviceAdded(device.getAddress());
            }
@@ -391,6 +415,8 @@ public class BluetoothDeviceManager {
                return;
            }
            if (targetDeviceMap.containsKey(device.getAddress())) {
                Log.i(this, "Removing device with address: " + device + " and devicetype="
                        + getDeviceTypeString(deviceType));
                targetDeviceMap.remove(device.getAddress());
                mBluetoothRouteManager.onDeviceLost(device.getAddress());
            }
@@ -568,50 +594,70 @@ public class BluetoothDeviceManager {
    // Connect audio to the bluetooth device at address, checking to see whether it's
    // le audio, hearing aid or a HFP device, and using the proper BT API.
    public boolean connectAudio(String address, boolean switchingBtDevices) {
        int callProfile = BluetoothProfile.LE_AUDIO;
        Log.i(this, "Telecomm connecting audio to device: " + address);
        BluetoothDevice device = null;
        if (mLeAudioDevicesByAddress.containsKey(address)) {
            Log.i(this, "Telecomm found LE Audio device for address: " + address);
            if (mBluetoothLeAudioService == null) {
                Log.w(this, "Attempting to turn on audio when the le audio service is null");
                return false;
            }
            BluetoothDevice device = mLeAudioDevicesByAddress.get(address);
            device = mLeAudioDevicesByAddress.get(address);
            callProfile = BluetoothProfile.LE_AUDIO;
        } else if (mHearingAidDevicesByAddress.containsKey(address)) {
            Log.i(this, "Telecomm found hearing aid device for address: " + address);
            if (mBluetoothHearingAid == null) {
                Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
                return false;
            }
            device = mHearingAidDevicesByAddress.get(address);
            callProfile = BluetoothProfile.HEARING_AID;
        } else if (mHfpDevicesByAddress.containsKey(address)) {
            Log.i(this, "Telecomm found HFP device for address: " + address);
            if (mBluetoothHeadset == null) {
                Log.w(this, "Attempting to turn on audio when the headset service is null");
                return false;
            }
            device = mHfpDevicesByAddress.get(address);
            callProfile = BluetoothProfile.HEADSET;
        }

        if (device == null) {
            Log.w(this, "No active profiles for Bluetooth address=" + address);
            return false;
        }

        Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
        if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
            && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
            Log.i(this, "Preferred duplex profile for device=" + address + " is "
                + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
            callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
        }

        if (callProfile == BluetoothProfile.LE_AUDIO) {
            if (mBluetoothAdapter.setActiveDevice(
                    device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {

                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                 * will be audio switched to is available to be choose as communication device */
                if (!switchingBtDevices) {
                    return setLeAudioCommunicationDevice();
                }

                return true;
            }
            return false;
        } else if (mHearingAidDevicesByAddress.containsKey(address)) {
            if (mBluetoothHearingAid == null) {
                Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
                return false;
            }
            if (mBluetoothAdapter.setActiveDevice(
                    mHearingAidDevicesByAddress.get(address),
                    BluetoothAdapter.ACTIVE_DEVICE_ALL)) {

        } else if (callProfile == BluetoothProfile.HEARING_AID) {
            if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                 * will be audio switched to is available to be choose as communication device */
                if (!switchingBtDevices) {
                    return setHearingAidCommunicationDevice();
                }

                return true;
            }
            return false;
        } else if (mHfpDevicesByAddress.containsKey(address)) {
            BluetoothDevice device = mHfpDevicesByAddress.get(address);
            if (mBluetoothHeadset == null) {
                Log.w(this, "Attempting to turn on audio when the headset service is null");
                return false;
            }
        } else if (callProfile == BluetoothProfile.HEADSET) {
            boolean success = mBluetoothAdapter.setActiveDevice(device,
                BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
            if (!success) {
+26 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.telecom.bluetooth;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
@@ -25,6 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.telecom.Log;
import android.telecom.Logging.Session;

@@ -83,7 +85,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver {
                intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                        BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
        BluetoothDevice device =
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
        if (device == null) {
            Log.w(LOG_TAG, "Got null device from broadcast. " +
                    "Ignoring.");
@@ -114,7 +116,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver {
        int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                BluetoothHeadset.STATE_DISCONNECTED);
        BluetoothDevice device =
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);

        if (device == null) {
            Log.w(LOG_TAG, "Got null device from broadcast. " +
@@ -148,7 +150,7 @@ public class BluetoothStateReceiver extends BroadcastReceiver {

    private void handleActiveDeviceChanged(Intent intent) {
        BluetoothDevice device =
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);

        int deviceType;
        if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
@@ -180,11 +182,31 @@ public class BluetoothStateReceiver extends BroadcastReceiver {
                }
                args.arg2 = device.getAddress();

                boolean usePreferredAudioProfile = false;
                BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter();
                int preferredDuplexProfile = BluetoothProfile.LE_AUDIO;
                if (bluetoothAdapter != null) {
                    Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles(
                            device);
                    if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
                            && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)
                            != 0) {
                        Log.i(this, "Preferred duplex profile for device=" + device + " is "
                                + preferredAudioProfiles.getInt(
                                BluetoothAdapter.AUDIO_MODE_DUPLEX));
                        usePreferredAudioProfile = true;
                        preferredDuplexProfile =
                                preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
                    }
                }

                if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
                    /* In Le Audio case, once device got Active, the Telecom needs to make sure it
                     * is set as communication device before we can say that BT_AUDIO_IS_ON
                     */
                    if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) {
                    if ((!usePreferredAudioProfile
                            || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
                            && !mBluetoothDeviceManager.setLeAudioCommunicationDevice()) {
                        Log.w(LOG_TAG,
                                "Device %s cannot be use as LE audio communication device.",
                                device);
+104 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
import android.content.Intent;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;

@@ -178,6 +179,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
        when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);

        receiverUnderTest.onReceive(mContext,
@@ -188,6 +190,7 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2);
        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6);
        when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
@@ -263,17 +266,19 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
    @Test
    public void testLeAudioDedup() {
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device1,
                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device6,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
        when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
        when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
        assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
        assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
    }
@@ -458,6 +463,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
        verify(mBluetoothHeadset, never()).connectAudio();
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_AUDIO));

        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5,
                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
@@ -485,6 +492,8 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
        verify(mBluetoothHeadset, never()).connectAudio();
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));

        when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
                .thenReturn(Arrays.asList(device5, device6));
@@ -497,6 +506,98 @@ public class BluetoothDeviceManagerTest extends TelecomTestCase {
        verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
    }

    @SmallTest
    @Test
    public void testConnectDualModeEarbud() {
        receiverUnderTest.setIsInCall(true);

        // LE Audio earbuds connected
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
        // HFP device connected
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);

        AudioDeviceInfo mockAudioDevice5Info = mock(AudioDeviceInfo.class);
        when(mockAudioDevice5Info.getAddress()).thenReturn(device5.getAddress());
        when(mockAudioDevice5Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
        AudioDeviceInfo mockAudioDevice6Info = mock(AudioDeviceInfo.class);
        when(mockAudioDevice6Info.getAddress()).thenReturn(device6.getAddress());
        when(mockAudioDevice6Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
        List<AudioDeviceInfo> devices = new ArrayList<>();
        devices.add(mockAudioDevice5Info);
        devices.add(mockAudioDevice6Info);

        when(mockAudioManager.getAvailableCommunicationDevices())
                .thenReturn(devices);
        when(mockAudioManager.setCommunicationDevice(mockAudioDevice5Info))
                .thenReturn(true);

        Bundle hfpPreferred = new Bundle();
        hfpPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET);
        Bundle leAudioPreferred = new Bundle();
        leAudioPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO);

        // TEST 1: LE Audio preferred for DUPLEX
        when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(leAudioPreferred);
        when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(leAudioPreferred);
        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
        verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
        verify(mBluetoothHeadset, never()).connectAudio();
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
        verify(mockAudioManager).setCommunicationDevice(mockAudioDevice5Info);

        when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
                .thenReturn(Arrays.asList(device5, device6));

        // Check disconnect during a call
        devices.remove(mockAudioDevice5Info);
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeRemoved(device5, 1);

        mBluetoothDeviceManager.connectAudio(device6.getAddress(), false);
        verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
        verify(mBluetoothHeadset, never()).connectAudio();
        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));

        // Reconnect other LE Audio earbud
        devices.add(mockAudioDevice5Info);
        receiverUnderTest.onReceive(mContext,
                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);

        // Disconnects audio
        mBluetoothDeviceManager.disconnectAudio();
        verify(mockAudioManager, times(1)).clearCommunicationDevice();
        verify(mBluetoothHeadset, times(1)).disconnectAudio();

        // TEST 2: HFP preferred for DUPLEX
        when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(hfpPreferred);
        when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(hfpPreferred);
        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL))).thenReturn(true);
        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
        verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
        verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
        verify(mBluetoothHeadset).connectAudio();
        mBluetoothDeviceManager.disconnectAudio();
        verify(mBluetoothHeadset, times(2)).disconnectAudio();
    }

    @SmallTest
    @Test
    public void testClearHearingAidCommunicationDevice() {