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

Commit b14c97b2 authored by Rahul Sabnis's avatar Rahul Sabnis Committed by Automerger Merge Worker
Browse files

Telecomm changes to support dual mode Bluetooth audio devices am: 03387266

parents 6a8c01cb 03387266
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() {