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

Commit 2825239c authored by Ugo Yu's avatar Ugo Yu Committed by William Escande
Browse files

Add a flag to connect to supported profiles after pairing

- Auto connect to supported profiles after pairing if the feature
  is enabled.
- Do not auto connect to HFP/A2DP if LE audio only mode is enabled.
- Add a property "bluetooth.phone_policy.auto_connect.config" to
  enable this feature.

Bug: 267411941
Test: atest BluetoothInstrumentationTests
(cherry picked from https://android-review.googlesource.com/q/commit:f7bf7a80b5b861c21ced95ea6257ba63c2c4b624)
Merged-In: If2ba19070d44b808e20ed1950cef27ff300f9ccf
Change-Id: If2ba19070d44b808e20ed1950cef27ff300f9ccf
Bug: 263323082
parent 78247c7c
Loading
Loading
Loading
Loading
+108 −48
Original line number Diff line number Diff line
@@ -96,8 +96,10 @@ class PhonePolicy {
    private static final int MESSAGE_PROFILE_ACTIVE_DEVICE_CHANGED = 5;
    private static final int MESSAGE_DEVICE_CONNECTED = 6;

    private static final String PREFER_LE_AUDIO_ONLY_MODE =
    @VisibleForTesting static final String PREFER_LE_AUDIO_ONLY_MODE =
            "persist.bluetooth.prefer_le_audio_only_mode";
    @VisibleForTesting static final String AUTO_CONNECT_PROFILES_PROPERTY =
            "bluetooth.auto_connect_profiles.enabled";

    // Timeouts
    @VisibleForTesting static int sConnectOtherProfilesTimeoutMillis = 6000; // 6s
@@ -110,7 +112,8 @@ class PhonePolicy {
    private final HashSet<BluetoothDevice> mA2dpRetrySet = new HashSet<>();
    private final HashSet<BluetoothDevice> mConnectOtherProfilesDeviceSet = new HashSet<>();

    private Boolean mPreferLeAudioOnlyMode = false;
    @VisibleForTesting boolean mPreferLeAudioOnlyMode;
    @VisibleForTesting boolean mAutoConnectProfilesSupported;

    // Broadcast receiver for all changes to states of various profiles
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -291,6 +294,8 @@ class PhonePolicy {
        mFactory = factory;
        mHandler = new PhonePolicyHandler(service.getMainLooper());
        mPreferLeAudioOnlyMode = SystemProperties.getBoolean(PREFER_LE_AUDIO_ONLY_MODE, true);
        mAutoConnectProfilesSupported = SystemProperties.getBoolean(
                AUTO_CONNECT_PROFILES_PROPERTY, false);
    }

    // Policy implementation, all functions MUST be private
@@ -311,75 +316,104 @@ class PhonePolicy {
        BassClientService bcService = mFactory.getBassClientService();
        BatteryService batteryService = mFactory.getBatteryService();

        boolean isLeAudioProfileAllowed = false;
        if ((leAudioService != null) && Utils.arrayContains(uuids,
                BluetoothUuid.LE_AUDIO) && (leAudioService.getConnectionPolicy(device)
                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            isLeAudioProfileAllowed = true;
        }

        // 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
        if ((hidService != null) && (Utils.arrayContains(uuids, BluetoothUuid.HID)
                || Utils.arrayContains(uuids, BluetoothUuid.HOGP)) && (
                hidService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            if (mAutoConnectProfilesSupported) {
                hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.HID_HOST, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }

        // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
        if ((headsetService != null) && ((Utils.arrayContains(uuids, BluetoothUuid.HSP)
                || Utils.arrayContains(uuids, BluetoothUuid.HFP)) && (
                headsetService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN))) {
            if (mPreferLeAudioOnlyMode && isLeAudioProfileAllowed) {
                debugLog("clear hfp profile priority for the le audio dual mode device "
                        + device);
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
            } else {
                if (mAutoConnectProfilesSupported) {
                    headsetService.setConnectionPolicy(device,
                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                } else {
                    mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                            BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                }
            }
        }

        if ((a2dpService != null) && (Utils.arrayContains(uuids, BluetoothUuid.A2DP_SINK)
                || Utils.arrayContains(uuids, BluetoothUuid.ADV_AUDIO_DIST)) && (
                a2dpService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            if (mPreferLeAudioOnlyMode && isLeAudioProfileAllowed) {
                debugLog("clear a2dp profile priority for the le audio dual mode device "
                        + device);
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
            } else {
                if (mAutoConnectProfilesSupported) {
                    a2dpService.setConnectionPolicy(device,
                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                } else {
                    mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                            BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                }
            }
        }

        // CSIP should be connected prior than LE Audio
        if ((csipSetCooridnatorService != null)
                && (Utils.arrayContains(uuids, BluetoothUuid.COORDINATED_SET))
                && (csipSetCooridnatorService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            if (mAutoConnectProfilesSupported) {
                csipSetCooridnatorService.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                    BluetoothProfile.CSIP_SET_COORDINATOR, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                        BluetoothProfile.CSIP_SET_COORDINATOR,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }

        // If we do not have a stored priority for HFP/A2DP (all roles) then default to on.
        if ((panService != null) && (Utils.arrayContains(uuids, BluetoothUuid.PANU) && (
                panService.getConnectionPolicy(device)
                        == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)
                && mAdapterService.getResources()
                .getBoolean(R.bool.config_bluetooth_pan_enable_autoconnect))) {
            if (mAutoConnectProfilesSupported) {
                panService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }

        boolean isLeAudioProfileAllowed = false;
        if ((leAudioService != null) && Utils.arrayContains(uuids,
                BluetoothUuid.LE_AUDIO) && (leAudioService.getConnectionPolicy(device)
                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
        if (isLeAudioProfileAllowed) {
            debugLog("setting le audio profile priority for device " + device);
            isLeAudioProfileAllowed = true;
            if (mAutoConnectProfilesSupported) {
                leAudioService.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.LE_AUDIO, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            if (mPreferLeAudioOnlyMode) {
                if (mAdapterService.getDatabase()
                        .getProfileConnectionPolicy(device, BluetoothProfile.A2DP)
                        >  BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
                    debugLog("clear a2dp profile priority for the le audio dual mode device "
                            + device);
                    mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                            BluetoothProfile.A2DP, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
                }
                if (mAdapterService.getDatabase()
                        .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)
                        >  BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
                    debugLog("clear hfp profile priority for the le audio dual mode device "
                            + device);
                    mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                            BluetoothProfile.HEADSET, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
                }
            }
        }

@@ -390,8 +424,14 @@ class PhonePolicy {
                debugLog("LE Audio preferred over ASHA for device " + device);
            } else {
                debugLog("setting hearing aid profile priority for device " + device);
                if (mAutoConnectProfilesSupported) {
                    hearingAidService.setConnectionPolicy(device,
                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                } else {
                    mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                            BluetoothProfile.HEARING_AID,
                            BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                }
            }
        }

@@ -399,34 +439,54 @@ class PhonePolicy {
                BluetoothUuid.VOLUME_CONTROL) && (volumeControlService.getConnectionPolicy(device)
                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            debugLog("setting volume control profile priority for device " + device);
            if (mAutoConnectProfilesSupported) {
                volumeControlService.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                    BluetoothProfile.VOLUME_CONTROL, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
                        BluetoothProfile.VOLUME_CONTROL,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }

        if ((hapClientService != null) && Utils.arrayContains(uuids,
                BluetoothUuid.HAS) && (hapClientService.getConnectionPolicy(device)
                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            debugLog("setting hearing access profile priority for device " + device);
            if (mAutoConnectProfilesSupported) {
                hapClientService.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.HAP_CLIENT, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }

        if ((bcService != null) && Utils.arrayContains(uuids,
                BluetoothUuid.BASS) && (bcService.getConnectionPolicy(device)
                == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            debugLog("setting broadcast assistant profile priority for device " + device);
            if (mAutoConnectProfilesSupported) {
                bcService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }
        if ((batteryService != null) && Utils.arrayContains(uuids,
                BluetoothUuid.BATTERY) && (batteryService.getConnectionPolicy(device)
                    == BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
            debugLog("setting battery profile priority for device " + device);
            if (mAutoConnectProfilesSupported) {
                batteryService.setConnectionPolicy(device,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            } else {
                mAdapterService.getDatabase().setProfileConnectionPolicy(device,
                        BluetoothProfile.BATTERY, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
            }
        }
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    private void processProfileStateChanged(BluetoothDevice device, int profileId, int nextState,
+114 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.le_audio.LeAudioService;

import org.junit.After;
import org.junit.Before;
@@ -67,6 +68,8 @@ public class PhonePolicyTest {
    @Mock private ServiceFactory mServiceFactory;
    @Mock private HeadsetService mHeadsetService;
    @Mock private A2dpService mA2dpService;
    @Mock private LeAudioService mLeAudioService;

    @Mock private DatabaseManager mDatabaseManager;

    @Before
@@ -83,6 +86,7 @@ public class PhonePolicyTest {
        // Setup the mocked factory to return mocked services
        doReturn(mHeadsetService).when(mServiceFactory).getHeadsetService();
        doReturn(mA2dpService).when(mServiceFactory).getA2dpService();
        doReturn(mLeAudioService).when(mServiceFactory).getLeAudioService();
        // Start handler thread for this test
        mHandlerThread = new HandlerThread("PhonePolicyTestHandlerThread");
        mHandlerThread.start();
@@ -111,6 +115,8 @@ public class PhonePolicyTest {
     */
    @Test
    public void testProcessInitProfilePriorities() {
        mPhonePolicy.mAutoConnectProfilesSupported = false;

        BluetoothDevice device = getTestDevice(mAdapter, 0);
        // Mock the HeadsetService to return unknown connection policy
        when(mHeadsetService.getConnectionPolicy(device))
@@ -140,6 +146,114 @@ public class PhonePolicyTest {
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }


    @Test
    public void testProcessInitProfilePriorities_WithAutoConnect() {
        mPhonePolicy.mAutoConnectProfilesSupported = true;

        BluetoothDevice device = getTestDevice(mAdapter, 0);
        // Mock the HeadsetService to return unknown connection policy
        when(mHeadsetService.getConnectionPolicy(device))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);

        // Mock the A2DP service to return undefined unknown connection policy
        when(mA2dpService.getConnectionPolicy(device))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);

        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);

        // Inject an event for UUIDs updated for a remote device with only HFP enabled
        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        ParcelUuid[] uuids = new ParcelUuid[2];
        uuids[0] = BluetoothUuid.HFP;
        uuids[1] = BluetoothUuid.A2DP_SINK;
        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);

        // Check auto connect
        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    @Test
    public void testProcessInitProfilePriorities_LeAudio() {
        BluetoothDevice device = getTestDevice(mAdapter, 0);

        // Auto connect to LE audio but disallow HFP and A2DP
        processInitProfilePriorities_LeAudioHelper(true, true);
        verify(mLeAudioService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);

        // Does not auto connect and disallow HFP and A2DP to be connected
        processInitProfilePriorities_LeAudioHelper(true, false);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2))
                .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2))
                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP,
                        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);

        // Auto connect to LE audio, HFP, A2DP
        processInitProfilePriorities_LeAudioHelper(false, true);
        verify(mLeAudioService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mA2dpService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);

        // Does not auto connect and allow HFP and A2DP to be connected
        processInitProfilePriorities_LeAudioHelper(false, false);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2))
                .setProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setProfileConnectionPolicy(device, BluetoothProfile.A2DP,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(1))
                .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET,
                        BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    private void processInitProfilePriorities_LeAudioHelper(
            boolean preferLeOnly, boolean autoConnect) {
        mPhonePolicy.mPreferLeAudioOnlyMode = preferLeOnly;
        mPhonePolicy.mAutoConnectProfilesSupported = autoConnect;

        BluetoothDevice device = getTestDevice(mAdapter, 0);
        // Mock the HFP, A2DP and LE audio services to return unknown connection policy
        when(mHeadsetService.getConnectionPolicy(device))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        when(mA2dpService.getConnectionPolicy(device))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        when(mLeAudioService.getConnectionPolicy(device))
                .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);

        when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);

        // Inject an event for UUIDs updated for a remote device with only HFP enabled
        Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        ParcelUuid[] uuids = new ParcelUuid[3];
        uuids[0] = BluetoothUuid.HFP;
        uuids[1] = BluetoothUuid.A2DP_SINK;
        uuids[2] = BluetoothUuid.LE_AUDIO;
        intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids);
        mPhonePolicy.getBroadcastReceiver().onReceive(null /* context */, intent);
    }

    /**
     * Test that when the adapter is turned ON then we call autoconnect on devices that have HFP and
     * A2DP enabled. NOTE that the assumption is that we have already done the pairing previously