Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +15 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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}. Loading android/app/src/com/android/bluetooth/btservice/PhonePolicy.java +151 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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: " Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); } Loading android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java +153 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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(); Loading @@ -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: Loading Loading @@ -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() { Loading Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +15 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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}. Loading
android/app/src/com/android/bluetooth/btservice/PhonePolicy.java +151 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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: " Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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); } Loading
android/app/tests/unit/src/com/android/bluetooth/btservice/PhonePolicyTest.java +153 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading @@ -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(); Loading @@ -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: Loading Loading @@ -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() { Loading