Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +88 −36 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import static java.util.stream.Collectors.toList; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.bluetooth.BluetoothAdapter; Loading Loading @@ -64,6 +66,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; Loading @@ -84,6 +87,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE"; public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING"; public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID = "bluetooth_le_broadcast_primary_device_group_id"; public static final int BROADCAST_STATE_UNKNOWN = 0; public static final int BROADCAST_STATE_ON = 1; public static final int BROADCAST_STATE_OFF = 2; Loading Loading @@ -1121,6 +1126,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { /** Update fallback active device if needed. */ public void updateFallbackActiveDeviceIfNeeded() { if (isWorkProfile(mContext)) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile."); return; } if (mServiceBroadcast == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null"); return; Loading @@ -1135,71 +1144,114 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null"); return; } List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast(); if (devicesInBroadcast.isEmpty()) { Map<Integer, List<BluetoothDevice>> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast(); if (deviceGroupsInBroadcast.isEmpty()) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast"); return; } List<BluetoothDevice> devices = BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices(); BluetoothDevice targetDevice = null; // Find the earliest connected device in sharing session. int targetDeviceIdx = -1; for (BluetoothDevice device : devicesInBroadcast) { if (devices.contains(device)) { int idx = devices.indexOf(device); if (idx > targetDeviceIdx) { targetDeviceIdx = idx; targetDevice = device; int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( mContext.getContentResolver()); if (Flags.audioSharingHysteresisModeFix()) { int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId(); if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) { if (userPreferredPrimaryGroupId == fallbackActiveGroupId) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already user preferred"); return; } else { targetGroupId = userPreferredPrimaryGroupId; } } if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { // If there is no user preferred primary device, set the earliest connected // device in sharing session as the fallback. targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast); } if (targetDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null"); return; } else { // Set the earliest connected device in sharing session as the fallback. targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast); } CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice); if (targetCachedDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device"); Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, target group id = " + targetGroupId); if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return; if (targetGroupId == fallbackActiveGroupId) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback"); return; } int fallbackActiveGroupId = getFallbackActiveGroupId(); if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) { Log.d( TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: " + fallbackActiveGroupId); CachedBluetoothDevice targetCachedDevice = getMainDevice( deviceGroupsInBroadcast.get(targetGroupId)); if (targetCachedDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find main device"); return; } Log.d( TAG, "updateFallbackActiveDeviceIfNeeded, set active device: " + targetDevice.getAnonymizedAddress()); + targetCachedDevice.getDevice()); targetCachedDevice.setActive(); } private List<BluetoothDevice> getDevicesInBroadcast() { @NonNull private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() { boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix(); List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices(); return connectedDevices.stream() .filter( bluetoothDevice -> { device -> { List<BluetoothLeBroadcastReceiveState> sourceList = mServiceBroadcastAssistant.getAllSources( bluetoothDevice); mServiceBroadcastAssistant.getAllSources(device); return !sourceList.isEmpty() && sourceList.stream().anyMatch( source -> hysteresisModeFixEnabled ? BluetoothUtils.isSourceMatched(source, mBroadcastId) : BluetoothUtils.isConnected(source)); }) .collect(Collectors.toList()); .collect(Collectors.groupingBy( device -> BluetoothUtils.getGroupId(mDeviceManager.findDevice(device)))); } private int getFallbackActiveGroupId() { private int getEarliestConnectedDeviceGroup( @NonNull Map<Integer, List<BluetoothDevice>> deviceGroups) { List<BluetoothDevice> devices = BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices(); // Find the earliest connected device in sharing session. int targetDeviceIdx = -1; int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; for (Map.Entry<Integer, List<BluetoothDevice>> entry : deviceGroups.entrySet()) { for (BluetoothDevice device : entry.getValue()) { if (devices.contains(device)) { int idx = devices.indexOf(device); if (idx > targetDeviceIdx) { targetDeviceIdx = idx; targetGroupId = entry.getKey(); } } } } return targetGroupId; } @Nullable private CachedBluetoothDevice getMainDevice(@Nullable List<BluetoothDevice> devices) { if (devices == null || devices.size() == 1) return null; List<CachedBluetoothDevice> cachedDevices = devices.stream() .map(device -> mDeviceManager.findDevice(device)) .filter(Objects::nonNull) .collect(toList()); for (CachedBluetoothDevice cachedDevice : cachedDevices) { if (!cachedDevice.getMemberDevice().isEmpty()) { return cachedDevice; } } CachedBluetoothDevice mainDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0); return mainDevice; } private int getUserPreferredPrimaryGroupId() { // TODO: use real key name in SettingsProvider return Settings.Secure.getInt( mContext.getContentResolver(), "bluetooth_le_broadcast_fallback_active_group_id", mContentResolver, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BluetoothCsipSetCoordinator.GROUP_ID_INVALID); } Loading packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +61 −84 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -36,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyManager; Loading Loading @@ -96,6 +98,8 @@ public class BluetoothEventManagerTest { private LocalBluetoothProfileManager mLocalProfileManager; @Mock private BluetoothUtils.ErrorListener mErrorListener; @Mock private LocalBluetoothLeBroadcast mBroadcast; private Context mContext; private Intent mIntent; Loading @@ -107,7 +111,7 @@ public class BluetoothEventManagerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mContext = spy(RuntimeEnvironment.application); mBluetoothEventManager = new BluetoothEventManager( Loading Loading @@ -208,28 +212,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ false, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -239,28 +229,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ false, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -270,28 +246,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(false); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ false, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -301,28 +263,31 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** * dispatchProfileConnectionStateChanged should not call {@link * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for * work profile. */ @Test public void dispatchProfileConnectionStateChanged_workProfile_noUpdateFallbackDevice() { setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ true); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(mBroadcast).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -332,28 +297,40 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() { setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(mBroadcast).updateFallbackActiveDeviceIfNeeded(); } private void setUpAudioSharing(boolean enableFlag, boolean enableFeature, boolean enableProfile, boolean workProfile) { if (enableFlag) { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); } else { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); } ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); int code = enableFeature ? BluetoothStatusCodes.FEATURE_SUPPORTED : BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(code); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(code); when(mBroadcast.isProfileReady()).thenReturn(enableProfile); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); when(assistant.isProfileReady()).thenReturn(enableProfile); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast).updateFallbackActiveDeviceIfNeeded(); UserManager userManager = mock(UserManager.class); when(mContext.getSystemService(UserManager.class)).thenReturn(userManager); when(userManager.isManagedProfile()).thenReturn(workProfile); } @Test Loading Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java +88 −36 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.settingslib.bluetooth; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import static java.util.stream.Collectors.toList; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.bluetooth.BluetoothAdapter; Loading Loading @@ -64,6 +66,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; Loading @@ -84,6 +87,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { public static final String EXTRA_BT_DEVICE_TO_AUTO_ADD_SOURCE = "BT_DEVICE_TO_AUTO_ADD_SOURCE"; public static final String EXTRA_START_LE_AUDIO_SHARING = "START_LE_AUDIO_SHARING"; public static final String EXTRA_PAIR_AND_JOIN_SHARING = "PAIR_AND_JOIN_SHARING"; public static final String BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID = "bluetooth_le_broadcast_primary_device_group_id"; public static final int BROADCAST_STATE_UNKNOWN = 0; public static final int BROADCAST_STATE_ON = 1; public static final int BROADCAST_STATE_OFF = 2; Loading Loading @@ -1121,6 +1126,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { /** Update fallback active device if needed. */ public void updateFallbackActiveDeviceIfNeeded() { if (isWorkProfile(mContext)) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile."); return; } if (mServiceBroadcast == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null"); return; Loading @@ -1135,71 +1144,114 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null"); return; } List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast(); if (devicesInBroadcast.isEmpty()) { Map<Integer, List<BluetoothDevice>> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast(); if (deviceGroupsInBroadcast.isEmpty()) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast"); return; } List<BluetoothDevice> devices = BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices(); BluetoothDevice targetDevice = null; // Find the earliest connected device in sharing session. int targetDeviceIdx = -1; for (BluetoothDevice device : devicesInBroadcast) { if (devices.contains(device)) { int idx = devices.indexOf(device); if (idx > targetDeviceIdx) { targetDeviceIdx = idx; targetDevice = device; int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( mContext.getContentResolver()); if (Flags.audioSharingHysteresisModeFix()) { int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId(); if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) { if (userPreferredPrimaryGroupId == fallbackActiveGroupId) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already user preferred"); return; } else { targetGroupId = userPreferredPrimaryGroupId; } } if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { // If there is no user preferred primary device, set the earliest connected // device in sharing session as the fallback. targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast); } if (targetDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null"); return; } else { // Set the earliest connected device in sharing session as the fallback. targetGroupId = getEarliestConnectedDeviceGroup(deviceGroupsInBroadcast); } CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice); if (targetCachedDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device"); Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, target group id = " + targetGroupId); if (targetGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return; if (targetGroupId == fallbackActiveGroupId) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback"); return; } int fallbackActiveGroupId = getFallbackActiveGroupId(); if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) { Log.d( TAG, "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: " + fallbackActiveGroupId); CachedBluetoothDevice targetCachedDevice = getMainDevice( deviceGroupsInBroadcast.get(targetGroupId)); if (targetCachedDevice == null) { Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find main device"); return; } Log.d( TAG, "updateFallbackActiveDeviceIfNeeded, set active device: " + targetDevice.getAnonymizedAddress()); + targetCachedDevice.getDevice()); targetCachedDevice.setActive(); } private List<BluetoothDevice> getDevicesInBroadcast() { @NonNull private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() { boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix(); List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices(); return connectedDevices.stream() .filter( bluetoothDevice -> { device -> { List<BluetoothLeBroadcastReceiveState> sourceList = mServiceBroadcastAssistant.getAllSources( bluetoothDevice); mServiceBroadcastAssistant.getAllSources(device); return !sourceList.isEmpty() && sourceList.stream().anyMatch( source -> hysteresisModeFixEnabled ? BluetoothUtils.isSourceMatched(source, mBroadcastId) : BluetoothUtils.isConnected(source)); }) .collect(Collectors.toList()); .collect(Collectors.groupingBy( device -> BluetoothUtils.getGroupId(mDeviceManager.findDevice(device)))); } private int getFallbackActiveGroupId() { private int getEarliestConnectedDeviceGroup( @NonNull Map<Integer, List<BluetoothDevice>> deviceGroups) { List<BluetoothDevice> devices = BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices(); // Find the earliest connected device in sharing session. int targetDeviceIdx = -1; int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; for (Map.Entry<Integer, List<BluetoothDevice>> entry : deviceGroups.entrySet()) { for (BluetoothDevice device : entry.getValue()) { if (devices.contains(device)) { int idx = devices.indexOf(device); if (idx > targetDeviceIdx) { targetDeviceIdx = idx; targetGroupId = entry.getKey(); } } } } return targetGroupId; } @Nullable private CachedBluetoothDevice getMainDevice(@Nullable List<BluetoothDevice> devices) { if (devices == null || devices.size() == 1) return null; List<CachedBluetoothDevice> cachedDevices = devices.stream() .map(device -> mDeviceManager.findDevice(device)) .filter(Objects::nonNull) .collect(toList()); for (CachedBluetoothDevice cachedDevice : cachedDevices) { if (!cachedDevice.getMemberDevice().isEmpty()) { return cachedDevice; } } CachedBluetoothDevice mainDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0); return mainDevice; } private int getUserPreferredPrimaryGroupId() { // TODO: use real key name in SettingsProvider return Settings.Secure.getInt( mContext.getContentResolver(), "bluetooth_le_broadcast_fallback_active_group_id", mContentResolver, BLUETOOTH_LE_BROADCAST_PRIMARY_DEVICE_GROUP_ID, BluetoothCsipSetCoordinator.GROUP_ID_INVALID); } Loading
packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +61 −84 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -36,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyManager; Loading Loading @@ -96,6 +98,8 @@ public class BluetoothEventManagerTest { private LocalBluetoothProfileManager mLocalProfileManager; @Mock private BluetoothUtils.ErrorListener mErrorListener; @Mock private LocalBluetoothLeBroadcast mBroadcast; private Context mContext; private Intent mIntent; Loading @@ -107,7 +111,7 @@ public class BluetoothEventManagerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mContext = spy(RuntimeEnvironment.application); mBluetoothEventManager = new BluetoothEventManager( Loading Loading @@ -208,28 +212,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ false, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -239,28 +229,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ false, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -270,28 +246,14 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(false); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ false, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -301,28 +263,31 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() { ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO); verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); verify(mBroadcast, times(0)).updateFallbackActiveDeviceIfNeeded(); } /** * dispatchProfileConnectionStateChanged should not call {@link * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for * work profile. */ @Test public void dispatchProfileConnectionStateChanged_workProfile_noUpdateFallbackDevice() { setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ true); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(mBroadcast).updateFallbackActiveDeviceIfNeeded(); } /** Loading @@ -332,28 +297,40 @@ public class BluetoothEventManagerTest { */ @Test public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() { setUpAudioSharing(/* enableFlag= */ true, /* enableFeature= */ true, /* enableProfile= */ true, /* workProfile= */ false); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(mBroadcast).updateFallbackActiveDeviceIfNeeded(); } private void setUpAudioSharing(boolean enableFlag, boolean enableFeature, boolean enableProfile, boolean workProfile) { if (enableFlag) { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); } else { mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); } ShadowBluetoothAdapter shadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING); LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class); when(broadcast.isProfileReady()).thenReturn(true); int code = enableFeature ? BluetoothStatusCodes.FEATURE_SUPPORTED : BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(code); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(code); when(mBroadcast.isProfileReady()).thenReturn(enableProfile); LocalBluetoothLeBroadcastAssistant assistant = mock(LocalBluetoothLeBroadcastAssistant.class); when(assistant.isProfileReady()).thenReturn(true); when(assistant.isProfileReady()).thenReturn(enableProfile); LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast); when(profileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant); when(mBtManager.getProfileManager()).thenReturn(profileManager); mBluetoothEventManager.dispatchProfileConnectionStateChanged( mCachedBluetoothDevice, BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT); verify(broadcast).updateFallbackActiveDeviceIfNeeded(); UserManager userManager = mock(UserManager.class); when(mContext.getSystemService(UserManager.class)).thenReturn(userManager); when(userManager.isManagedProfile()).thenReturn(workProfile); } @Test Loading