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

Commit 6c57a9e3 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "PhonePolicy: Allow LeAudio only devices" into main am: 41af6118 am: 0d856545

parents 106d3675 0d856545
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -134,8 +134,8 @@ import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.hid.HidDeviceService;
import com.android.bluetooth.hid.HidHostService;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.bluetooth.le_scan.ScanManager;
import com.android.bluetooth.le_scan.ScanController;
import com.android.bluetooth.le_scan.ScanManager;
import com.android.bluetooth.map.BluetoothMapService;
import com.android.bluetooth.mapclient.MapClientService;
import com.android.bluetooth.mcp.McpService;
@@ -3383,10 +3383,7 @@ public class AdapterService extends Service {
                return BluetoothDevice.DEVICE_TYPE_UNKNOWN;
            }

            DeviceProperties deviceProp = service.mRemoteDevices.getDeviceProperties(device);
            return deviceProp != null
                    ? deviceProp.getDeviceType()
                    : BluetoothDevice.DEVICE_TYPE_UNKNOWN;
            return service.getRemoteType(device);
        }

        @Override
@@ -8111,6 +8108,19 @@ public class AdapterService extends Service {
        return true;
    }

    /**
     * Get type of the remote device
     *
     * @param device the device to check
     * @return int device type
     */
    public int getRemoteType(BluetoothDevice device) {
        DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
        return deviceProp != null
                ? deviceProp.getDeviceType()
                : BluetoothDevice.DEVICE_TYPE_UNKNOWN;
    }

    /**
     * Sends service discovery UUIDs internally within the stack. This is meant to remove internal
     * dependencies on the broadcast {@link BluetoothDevice#ACTION_UUID}.
+151 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.bluetooth.Utils.isDualModeAudioEnabled;

import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
@@ -181,6 +182,104 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback {
                SystemProperties.getBoolean(LE_AUDIO_CONNECTION_BY_DEFAULT_PROPERTY, true);
    }

    boolean isLeAudioOnlyGroup(BluetoothDevice device) {
        if (!Flags.leaudioAllowLeaudioOnlyDevices()) {
            debugLog(" leaudio_allow_leaudio_only_devices is not enabled ");
            return false;
        }

        CsipSetCoordinatorService csipSetCoordinatorService =
                mFactory.getCsipSetCoordinatorService();

        if (csipSetCoordinatorService == null) {
            debugLog("isLeAudioOnlyGroup: no csip service known yet for " + device);
            return false;
        }

        int groupId = csipSetCoordinatorService.getGroupId(device, BluetoothUuid.CAP);
        if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
            debugLog("isLeAudioOnlyGroup: no LeAudio groupID yet known for " + device);
            return false;
        }

        int groupSize = csipSetCoordinatorService.getDesiredGroupSize(groupId);
        List<BluetoothDevice> groupDevices =
                csipSetCoordinatorService.getGroupDevicesOrdered(groupId);

        if (groupDevices.size() != groupSize) {
            debugLog(
                    "isLeAudioOnlyGroup: Group is not yet complete ("
                            + groupDevices.size()
                            + " != "
                            + groupSize
                            + ")");
            return false;
        }

        for (BluetoothDevice dev : groupDevices) {
            int remoteType = mAdapterService.getRemoteType(dev);
            debugLog("isLeAudioOnlyGroup: " + dev + " is type: " + remoteType);

            if (remoteType != BluetoothDevice.DEVICE_TYPE_LE) {
                debugLog("isLeAudioOnlyGroup: " + dev + " is type: " + remoteType);
                return false;
            }

            if (!mAdapterService.isProfileSupported(dev, BluetoothProfile.LE_AUDIO)) {
                debugLog("isLeAudioOnlyGroup: " + dev + " does not support LE AUDIO");
                return false;
            }

            if (mAdapterService.isProfileSupported(dev, BluetoothProfile.HEARING_AID)) {
                debugLog("isLeAudioOnlyGroup: " + dev + " supports ASHA");
                return false;
            }
        }

        return true;
    }

    boolean isLeAudioOnlyDevice(BluetoothDevice device, ParcelUuid[] uuids) {
        /* This functions checks if device belongs to the LeAudio group which
         * is LeAudio only. This is either
         * - LeAudio only Headset (no BR/EDR mode)
         * - LeAudio Hearing Aid  (no ASHA)
         *
         * Note, that we need to have all set bonded to take the decision.
         * If the set is not bonded, we cannot assume that.
         */

        if (!Flags.leaudioAllowLeaudioOnlyDevices()) {
            debugLog(" leaudio_allow_leaudio_only_devices is not enabled ");
            return false;
        }

        if (!Utils.arrayContains(uuids, BluetoothUuid.LE_AUDIO)) {
            return false;
        }

        int deviceType = mAdapterService.getRemoteType(device);

        if (deviceType != BluetoothDevice.DEVICE_TYPE_LE) {
            debugLog("isLeAudioOnlyDevice: " + device + " is type" + deviceType);
            return false;
        }

        if (Utils.arrayContains(uuids, BluetoothUuid.HEARING_AID)) {
            debugLog("isLeAudioOnlyDevice: " + device + " supports ASHA");
            return false;
        }

        /* For no CSIS device, allow LE Only devices. */
        if (!Utils.arrayContains(uuids, BluetoothUuid.COORDINATED_SET)) {
            debugLog("isLeAudioOnlyDevice: " + device + " is LeAudio only.");
            return true;
        }

        // For CSIS devices it is bit harder to check.
        return isLeAudioOnlyGroup(device);
    }

    // Policy implementation, all functions MUST be private
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    private void processInitProfilePriorities(BluetoothDevice device, ParcelUuid[] uuids) {
@@ -202,13 +301,16 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback {
        final boolean isBypassLeAudioAllowlist =
                SystemProperties.getBoolean(BYPASS_LE_AUDIO_ALLOWLIST_PROPERTY, false);

        boolean isLeAudioOnly = isLeAudioOnlyDevice(device, uuids);
        boolean isLeAudioProfileAllowed =
                (leAudioService != null)
                        && Utils.arrayContains(uuids, BluetoothUuid.LE_AUDIO)
                        && (leAudioService.getConnectionPolicy(device)
                                != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
                        && (mLeAudioEnabledByDefault || isDualModeAudioEnabled())
                        && (isBypassLeAudioAllowlist || mAdapterService.isLeAudioAllowed(device));
                        && (isBypassLeAudioAllowlist
                                || mAdapterService.isLeAudioAllowed(device)
                                || isLeAudioOnly);

        debugLog(
                "mLeAudioEnabledByDefault: "
@@ -216,7 +318,13 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback {
                        + ", isBypassLeAudioAllowlist: "
                        + isBypassLeAudioAllowlist
                        + ", isLeAudioAllowDevice: "
                        + mAdapterService.isLeAudioAllowed(device));
                        + mAdapterService.isLeAudioAllowed(device)
                        + ", mAutoConnectProfilesSupported: "
                        + mAutoConnectProfilesSupported
                        + ", isLeAudioProfileAllowed: "
                        + isLeAudioProfileAllowed
                        + ", isLeAudioOnly: "
                        + isLeAudioOnly);

        // Set profile priorities only for the profiles discovered on the remote device.
        // This avoids needless auto-connect attempts to profiles non-existent on the remote device
@@ -475,6 +583,44 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback {
        }
    }

    void handleLeAudioOnlyDeviceAfterCsipConnect(BluetoothDevice device) {
        debugLog("handleLeAudioOnlyDeviceAfterCsipConnect: " + device);

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService == null
                || (leAudioService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_ALLOWED)
                || !mAdapterService.isProfileSupported(device, BluetoothProfile.LE_AUDIO)) {
            debugLog("handleLeAudioOnlyDeviceAfterCsipConnect: nothing to do for " + device);
            return;
        }

        if (!isLeAudioOnlyGroup(device)) {
            /* Log no needed as above function will log on error. */
            return;
        }

        CsipSetCoordinatorService csipSetCoordinatorService =
                mFactory.getCsipSetCoordinatorService();
        /* Since isLeAudioOnlyGroup return true it means csipSetCoordinatorService is valid */
        List<BluetoothDevice> groupDevices =
                csipSetCoordinatorService.getGroupDevicesOrdered(
                        csipSetCoordinatorService.getGroupId(device, BluetoothUuid.CAP));

        debugLog("handleLeAudioOnlyDeviceAfterCsipConnect: enabling LeAudioOnlyDevice");
        for (BluetoothDevice dev : groupDevices) {
            if (leAudioService.getConnectionPolicy(dev)
                    != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
                /* Setting LeAudio service as allowed is sufficient,
                 * because other LeAudio services e.g. VC will
                 * be enabled by LeAudio service automatically.
                 */
                debugLog("...." + dev);
                leAudioService.setConnectionPolicy(dev, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
            int prevState) {
@@ -495,6 +641,9 @@ public class PhonePolicy implements AdapterService.BluetoothStateCallback {
                    case BluetoothProfile.HEADSET:
                        mHeadsetRetrySet.remove(device);
                        break;
                    case BluetoothProfile.CSIP_SET_COORDINATOR:
                        handleLeAudioOnlyDeviceAfterCsipConnect(device);
                        break;
                }
                connectOtherProfile(device);
            }
+153 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.bluetooth.TestUtils.waitForLooperToFinishScheduledTask
import static org.mockito.Mockito.*;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
@@ -40,6 +41,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.btservice.storage.MetadataDatabase;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;
@@ -81,9 +83,14 @@ public class PhonePolicyTest {
    @Mock private A2dpService mA2dpService;
    @Mock private LeAudioService mLeAudioService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private CsipSetCoordinatorService mCsipSetCoordinatorService;

    private List<BluetoothDevice> mLeAudioAllowedConnectionPolicyList = new ArrayList<>();

    @Before
    public void setUp() throws Exception {
        mLeAudioAllowedConnectionPolicyList.clear();

        // Stub A2DP and HFP
        when(mHeadsetService.connect(any(BluetoothDevice.class))).thenReturn(true);
        when(mA2dpService.connect(any(BluetoothDevice.class))).thenReturn(true);
@@ -96,6 +103,8 @@ public class PhonePolicyTest {
        doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService();
        doReturn(mA2dpService).when(mServiceFactory).getA2dpService();
        doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService();
        doReturn(mCsipSetCoordinatorService).when(mServiceFactory).getCsipSetCoordinatorService();

        // Start handler thread for this test
        mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
        mHandlerThread.start();
@@ -120,6 +129,18 @@ public class PhonePolicyTest {
        Utils.setDualModeAudioStateForTesting(mOriginalDualModeState);
    }

    int getLeAudioConnectionPolicy(BluetoothDevice dev) {
        if (!mLeAudioAllowedConnectionPolicyList.contains(dev)) {
            return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
        }
        return BluetoothProfile.CONNECTION_POLICY_ALLOWED;
    }

    boolean setLeAudioAllowedConnectionPolicy(BluetoothDevice dev) {
        mLeAudioAllowedConnectionPolicyList.add(dev);
        return true;
    }

    /**
     * Test that when new UUIDs are refreshed for a device then we set the priorities for various
     * profiles accurately. The following profiles should have ON priorities:
@@ -155,6 +176,138 @@ public class PhonePolicyTest {
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    private void processInitProfilePriorities_LeAudioOnlyHelper(
            int csipGroupId, int groupSize, boolean dualMode, boolean ashaDevice) {
        Utils.setDualModeAudioStateForTesting(false);
        mPhonePolicy.mLeAudioEnabledByDefault = true;
        mPhonePolicy.mAutoConnectProfilesSupported = true;
        SystemProperties.set(
                PhonePolicy.BYPASS_LE_AUDIO_ALLOWLIST_PROPERTY, Boolean.toString(false));

        int testedDeviceType = BluetoothDevice.DEVICE_TYPE_LE;
        if (dualMode) {
            /* If CSIP mode, use DUAL type only for single device */
            testedDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL;
        }

        List<BluetoothDevice> allConnectedDevices = new ArrayList<>();
        for (int i = 0; i < groupSize; i++) {
            BluetoothDevice device = getTestDevice(mAdapter, i);
            allConnectedDevices.add(device);
        }

        when(mCsipSetCoordinatorService.getGroupId(any(), any())).thenReturn(csipGroupId);
        when(mCsipSetCoordinatorService.getDesiredGroupSize(csipGroupId)).thenReturn(groupSize);

        /* Build list of test UUIDs */
        int numOfServices = 1;
        if (groupSize > 1) {
            numOfServices++;
        }
        if (ashaDevice) {
            numOfServices++;
        }
        ParcelUuid[] uuids = new ParcelUuid[numOfServices];
        int iter = 0;
        uuids[iter++] = BluetoothUuid.LE_AUDIO;
        if (groupSize > 1) {
            uuids[iter++] = BluetoothUuid.COORDINATED_SET;
        }
        if (ashaDevice) {
            uuids[iter++] = BluetoothUuid.HEARING_AID;
        }

        List<BluetoothDevice> connectedDevices = new ArrayList<>();

        for (BluetoothDevice dev : allConnectedDevices) {
            // Mock the HFP, A2DP and LE audio services to return unknown connection policy
            when(mHeadsetService.getConnectionPolicy(dev))
                    .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
            when(mA2dpService.getConnectionPolicy(dev))
                    .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);

            when(mLeAudioService.setConnectionPolicy(
                            dev, BluetoothProfile.CONNECTION_POLICY_ALLOWED))
                    .thenAnswer(
                            invocation -> {
                                return setLeAudioAllowedConnectionPolicy(dev);
                            });
            when(mLeAudioService.getConnectionPolicy(dev))
                    .thenAnswer(
                            invocation -> {
                                return getLeAudioConnectionPolicy(dev);
                            });

            when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
            when(mAdapterService.getRemoteUuids(dev)).thenReturn(uuids);
            when(mAdapterService.isProfileSupported(dev, BluetoothProfile.LE_AUDIO))
                    .thenReturn(true);
            when(mAdapterService.isProfileSupported(dev, BluetoothProfile.HEARING_AID))
                    .thenReturn(ashaDevice);

            /* First device is always LE only second depends on dualMode */
            if (groupSize == 1 || connectedDevices.size() >= 1) {
                when(mAdapterService.getRemoteType(dev)).thenReturn(testedDeviceType);
            } else {
                when(mAdapterService.getRemoteType(dev)).thenReturn(BluetoothDevice.DEVICE_TYPE_LE);
            }

            when(mCsipSetCoordinatorService.getGroupDevicesOrdered(csipGroupId))
                    .thenReturn(connectedDevices);
            mPhonePolicy.onUuidsDiscovered(dev, uuids);
            if (groupSize > 1) {
                connectedDevices.add(dev);
                // Simulate CSIP connection
                mPhonePolicy.profileConnectionStateChanged(
                        BluetoothProfile.CSIP_SET_COORDINATOR,
                        dev,
                        BluetoothProfile.STATE_DISCONNECTED,
                        BluetoothProfile.STATE_CONNECTED);
                waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
            }
        }
    }

    @Test
    public void testConnectLeAudioOnlyDevices_BandedHeadphones() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOW_LEAUDIO_ONLY_DEVICES);
        // Single device, no CSIP
        processInitProfilePriorities_LeAudioOnlyHelper(
                BluetoothCsipSetCoordinator.GROUP_ID_INVALID, 1, false, false);
        verify(mLeAudioService, times(1))
                .setConnectionPolicy(
                        any(BluetoothDevice.class), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
    }

    @Test
    public void testConnectLeAudioOnlyDevices_CsipSet() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOW_LEAUDIO_ONLY_DEVICES);
        // CSIP Le Audio only devices
        processInitProfilePriorities_LeAudioOnlyHelper(1, 2, false, false);
        verify(mLeAudioService, times(2))
                .setConnectionPolicy(
                        any(BluetoothDevice.class), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
    }

    @Test
    public void testConnectLeAudioOnlyDevices_DualModeCsipSet() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOW_LEAUDIO_ONLY_DEVICES);
        // CSIP Dual mode devices
        processInitProfilePriorities_LeAudioOnlyHelper(1, 2, true, false);
        verify(mLeAudioService, times(0))
                .setConnectionPolicy(
                        any(BluetoothDevice.class), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
    }

    @Test
    public void testConnectLeAudioOnlyDevices_AshaAndCsipSet() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOW_LEAUDIO_ONLY_DEVICES);
        // CSIP Dual mode devices
        processInitProfilePriorities_LeAudioOnlyHelper(1, 2, false, true);
        verify(mLeAudioService, times(0))
                .setConnectionPolicy(
                        any(BluetoothDevice.class), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
    }

    @Test
    public void testProcessInitProfilePriorities_WithAutoConnect() {