diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java index 33c1a6c6cb6..168fce51dc4 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingCallAudioPreferenceController.java @@ -66,7 +66,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** PreferenceController to control the dialog to choose the active device for calls and alarms */ public class AudioSharingCallAudioPreferenceController extends AudioSharingBasePreferenceController implements BluetoothCallback { - private static final String TAG = "CallsAndAlarmsPreferenceController"; + private static final String TAG = "CallAudioPrefController"; private static final String PREF_KEY = "calls_and_alarms"; @VisibleForTesting @@ -85,7 +85,7 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP private final ContentObserver mSettingsObserver; private final MetricsFeatureProvider mMetricsFeatureProvider; @Nullable private Fragment mFragment; - Map> mGroupedConnectedDevices = new HashMap<>(); + Map> mGroupedConnectedDevices = new HashMap<>(); private List mDeviceItemsInSharingSession = new ArrayList<>(); private final AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false); @@ -210,17 +210,18 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP "Skip set fallback active device: unchanged"); return; } - List devices = + List devices = mGroupedConnectedDevices.getOrDefault( item.getGroupId(), ImmutableList.of()); CachedBluetoothDevice lead = - AudioSharingUtils.getLeadDevice(devices); + AudioSharingUtils.getLeadDevice( + mCacheManager, devices); if (lead != null) { Log.d( TAG, "Set fallback active device: " + lead.getDevice() - .getAnonymizedAddress()); + .getAnonymizedAddress()); lead.setActive(); logCallAudioDeviceChange(currentGroupId, lead); } else { @@ -347,8 +348,8 @@ public class AudioSharingCallAudioPreferenceController extends AudioSharingBaseP */ private void updateSummary() { updateDeviceItemsInSharingSession(); - int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast( - mContext.getContentResolver()); + int fallbackActiveGroupId = + BluetoothUtils.getPrimaryGroupIdForBroadcast(mContext.getContentResolver()); if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { for (AudioSharingDeviceItem item : mDeviceItemsInSharingSession) { if (item.getGroupId() == fallbackActiveGroupId) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java index 472cb440063..4ee405d0f35 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogHandler.java @@ -16,6 +16,8 @@ package com.android.settings.connecteddevice.audiosharing; +import static java.util.stream.Collectors.toList; + import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; @@ -38,6 +40,7 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -49,14 +52,14 @@ import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; public class AudioSharingDialogHandler { - private static final String TAG = "AudioSharingDialogHandler"; + private static final String TAG = "AudioSharingDlgHandler"; private final Context mContext; private final Fragment mHostFragment; @Nullable private final LocalBluetoothManager mLocalBtManager; + @Nullable private final CachedBluetoothDeviceManager mDeviceManager; @Nullable private final LocalBluetoothLeBroadcast mBroadcast; @Nullable private final LocalBluetoothLeBroadcastAssistant mAssistant; private final MetricsFeatureProvider mMetricsFeatureProvider; @@ -163,6 +166,7 @@ public class AudioSharingDialogHandler { mContext = context; mHostFragment = fragment; mLocalBtManager = Utils.getLocalBluetoothManager(context); + mDeviceManager = mLocalBtManager != null ? mLocalBtManager.getCachedDeviceManager() : null; mBroadcast = mLocalBtManager != null ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile() @@ -212,7 +216,7 @@ public class AudioSharingDialogHandler { if (isBroadcasting) { // Show stop audio sharing dialog when an ineligible (non LE audio) remote device // connected during a sharing session. - Map> groupedDevices = + Map> groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); List deviceItemsInSharingSession = AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( @@ -256,19 +260,19 @@ public class AudioSharingDialogHandler { @NonNull CachedBluetoothDevice cachedDevice, boolean isBroadcasting, boolean userTriggered) { - Map> groupedDevices = + Map> groupedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); BluetoothDevice btDevice = cachedDevice.getDevice(); String deviceAddress = btDevice == null ? "" : btDevice.getAnonymizedAddress(); + int groupId = BluetoothUtils.getGroupId(cachedDevice); if (isBroadcasting) { // If another device within the same is already in the sharing session, add source to // the device automatically. - int groupId = BluetoothUtils.getGroupId(cachedDevice); if (groupedDevices.containsKey(groupId) && groupedDevices.get(groupId).stream() .anyMatch( device -> - BluetoothUtils.hasConnectedBroadcastSource( + BluetoothUtils.hasConnectedBroadcastSourceForBtDevice( device, mLocalBtManager))) { Log.d( TAG, @@ -352,14 +356,17 @@ public class AudioSharingDialogHandler { } else { // Build a list of AudioSharingDeviceItem for connected devices other than cachedDevice. List deviceItems = new ArrayList<>(); - for (List devices : groupedDevices.values()) { + for (Map.Entry> entry : groupedDevices.entrySet()) { + if (entry.getKey() == groupId) continue; // Use random device in the group within the sharing session to represent the group. - CachedBluetoothDevice device = devices.get(0); - if (BluetoothUtils.getGroupId(device) - == BluetoothUtils.getGroupId(cachedDevice)) { - continue; + for (BluetoothDevice device : entry.getValue()) { + CachedBluetoothDevice cDevice = + mDeviceManager != null ? mDeviceManager.findDevice(device) : null; + if (cDevice != null) { + deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(cDevice)); + break; + } } - deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device)); } // Show audio sharing join dialog when the second eligible (LE audio) remote // device connect and no sharing session. @@ -368,13 +375,10 @@ public class AudioSharingDialogHandler { new AudioSharingJoinDialogFragment.DialogEventListener() { @Override public void onShareClick() { - mTargetSinks = new ArrayList<>(); - for (List devices : - groupedDevices.values()) { - for (CachedBluetoothDevice device : devices) { - mTargetSinks.add(device.getDevice()); - } - } + mTargetSinks = + groupedDevices.values().stream() + .flatMap(items -> items.stream()) + .collect(toList()); Log.d(TAG, "Start broadcast with sinks = " + mTargetSinks.size()); if (mBroadcast != null) { mBroadcast.startPrivateBroadcast(); @@ -493,7 +497,7 @@ public class AudioSharingDialogHandler { } private void removeSourceForGroup( - int groupId, Map> groupedDevices) { + int groupId, Map> groupedDevices) { if (mAssistant == null) { Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); return; @@ -503,8 +507,6 @@ public class AudioSharingDialogHandler { return; } groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) .forEach( device -> { for (BluetoothLeBroadcastReceiveState source : @@ -515,7 +517,7 @@ public class AudioSharingDialogHandler { } private void addSourceForGroup( - int groupId, Map> groupedDevices) { + int groupId, Map> groupedDevices) { if (mBroadcast == null || mAssistant == null) { Log.d(TAG, "Fail to add source due to null profiles, group = " + groupId); return; @@ -525,8 +527,6 @@ public class AudioSharingDialogHandler { return; } groupedDevices.getOrDefault(groupId, ImmutableList.of()).stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) .forEach( device -> mAssistant.addSource( diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 3c0faba5db8..8396e48afd1 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -48,7 +48,6 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.SettingsMainSwitchBar; import com.android.settingslib.bluetooth.BluetoothUtils; -import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; @@ -63,17 +62,15 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; public class AudioSharingSwitchBarController extends BasePreferenceController implements DefaultLifecycleObserver, - OnCheckedChangeListener, - LocalBluetoothProfileManager.ServiceListener { - private static final String TAG = "AudioSharingSwitchBarCtl"; + OnCheckedChangeListener, + LocalBluetoothProfileManager.ServiceListener { + private static final String TAG = "AudioSharingSwitchCtlr"; private static final String PREF_KEY = "audio_sharing_main_switch"; interface OnAudioSharingStateChangedListener { @@ -103,7 +100,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController private final Executor mExecutor; private final MetricsFeatureProvider mMetricsFeatureProvider; private final OnAudioSharingStateChangedListener mListener; - private Map> mGroupedConnectedDevices = new HashMap<>(); + private Map> mGroupedConnectedDevices = new HashMap<>(); private List mTargetActiveSinks = new ArrayList<>(); private List mDeviceItemsForSharing = new ArrayList<>(); @VisibleForTesting IntentFilter mIntentFilter; @@ -341,8 +338,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController // FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST is always true in // prod. We can turn off the flag for debug purpose. if (FeatureFlagUtils.isEnabled( - mContext, - FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) + mContext, + FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST) && mAssistant.getAllConnectedDevices().isEmpty()) { // Pop up dialog to ask users to connect at least one lea buds before audio sharing. AudioSharingUtils.postOnMainThread( @@ -454,13 +451,11 @@ public class AudioSharingSwitchBarController extends BasePreferenceController mDeviceItemsForSharing = new ArrayList<>(deviceItems); mTargetActiveSinks = new ArrayList<>(); if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) { - for (CachedBluetoothDevice device : + // If active device exists for audio sharing, share to it + // automatically once the broadcast is started. + mTargetActiveSinks = mGroupedConnectedDevices.getOrDefault( - deviceItems.get(0).getGroupId(), ImmutableList.of())) { - // If active device exists for audio sharing, share to it - // automatically once the broadcast is started. - mTargetActiveSinks.add(device.getDevice()); - } + deviceItems.get(0).getGroupId(), ImmutableList.of()); mDeviceItemsForSharing.remove(0); } if (mBroadcast != null) { @@ -488,7 +483,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController boolean isStateReady = isBluetoothOn() && AudioSharingUtils.isAudioSharingProfileReady( - mProfileManager); + mProfileManager); AudioSharingUtils.postOnMainThread( mContext, () -> { @@ -541,12 +536,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController @Override public void onItemClick(@NonNull AudioSharingDeviceItem item) { AudioSharingUtils.addSourceToTargetSinks( - mGroupedConnectedDevices - .getOrDefault(item.getGroupId(), ImmutableList.of()) - .stream() - .map(CachedBluetoothDevice::getDevice) - .filter(Objects::nonNull) - .collect(Collectors.toList()), + mGroupedConnectedDevices.getOrDefault( + item.getGroupId(), ImmutableList.of()), mBtManager); mGroupedConnectedDevices.clear(); mDeviceItemsForSharing.clear(); @@ -575,8 +566,8 @@ public class AudioSharingSwitchBarController extends BasePreferenceController @NonNull ViewGroup host, @NonNull View view, @NonNull AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && (event.getContentChangeTypes() - & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED) - != 0) { + & AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED) + != 0) { Log.d(TAG, "Skip accessibility event for CONTENT_CHANGE_TYPE_ENABLED"); return false; } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index 50f9c9a8cc9..0c2dc367171 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -22,6 +22,8 @@ import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtil import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_SOURCE_PAGE_ID; import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.MetricKey.METRIC_KEY_USER_TRIGGERED; +import static java.util.stream.Collectors.toList; + import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; @@ -44,10 +46,11 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.VolumeControlProfile; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Objects; public class AudioSharingUtils { private static final String TAG = "AudioSharingUtils"; @@ -62,15 +65,15 @@ public class AudioSharingUtils { } /** - * Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are - * grouped by CSIP group id. + * Fetch {@link BluetoothDevice}s connected to the broadcast assistant. The devices are grouped + * by CSIP group id. * * @param localBtManager The BT manager to provide BT functions. * @return A map of connected devices grouped by CSIP group id. */ - public static Map> fetchConnectedDevicesByGroupId( + public static Map> fetchConnectedDevicesByGroupId( @Nullable LocalBluetoothManager localBtManager) { - Map> groupedDevices = new HashMap<>(); + Map> groupedDevices = new HashMap<>(); if (localBtManager == null) { Log.d(TAG, "Skip fetchConnectedDevicesByGroupId due to bt manager is null"); return groupedDevices; @@ -99,7 +102,7 @@ public class AudioSharingUtils { if (!groupedDevices.containsKey(groupId)) { groupedDevices.put(groupId, new ArrayList<>()); } - groupedDevices.get(groupId).add(cachedDevice); + groupedDevices.get(groupId).add(device); } if (DEBUG) { Log.d(TAG, "fetchConnectedDevicesByGroupId: " + groupedDevices); @@ -122,11 +125,16 @@ public class AudioSharingUtils { */ public static List buildOrderedConnectedLeadDevices( @Nullable LocalBluetoothManager localBtManager, - Map> groupedConnectedDevices, + Map> groupedConnectedDevices, boolean filterByInSharing) { List orderedDevices = new ArrayList<>(); - for (List devices : groupedConnectedDevices.values()) { - CachedBluetoothDevice leadDevice = getLeadDevice(devices); + if (localBtManager == null) { + Log.d(TAG, "Skip buildOrderedConnectedLeadDevices due to bt manager is null"); + return orderedDevices; + } + CachedBluetoothDeviceManager deviceManager = localBtManager.getCachedDeviceManager(); + for (List devices : groupedConnectedDevices.values()) { + CachedBluetoothDevice leadDevice = getLeadDevice(deviceManager, devices); if (leadDevice == null) { Log.d(TAG, "Skip due to no lead device"); continue; @@ -141,52 +149,39 @@ public class AudioSharingUtils { } orderedDevices.add(leadDevice); } - orderedDevices.sort( - (CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> { - // Active above not inactive - int comparison = - (isActiveLeAudioDevice(d2) ? 1 : 0) - - (isActiveLeAudioDevice(d1) ? 1 : 0); - if (comparison != 0) return comparison; - // Bonded above not bonded - comparison = - (d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - - (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); - if (comparison != 0) return comparison; - // Bond timestamp available above unavailable - comparison = - (d2.getBondTimestamp() != null ? 1 : 0) - - (d1.getBondTimestamp() != null ? 1 : 0); - if (comparison != 0) return comparison; - // Order by bond timestamp if it is available - // Otherwise order by device name - return d1.getBondTimestamp() != null - ? d1.getBondTimestamp().compareTo(d2.getBondTimestamp()) - : d1.getName().compareTo(d2.getName()); - }); + orderedDevices.sort(sCachedDeviceComparator); return orderedDevices; } /** * Get the lead device from a list of devices with same group id. * + * @param deviceManager CachedBluetoothDeviceManager * @param devices A list of devices with same group id. * @return The lead device */ @Nullable public static CachedBluetoothDevice getLeadDevice( - @NonNull List devices) { - if (devices.isEmpty()) return null; - for (CachedBluetoothDevice device : devices) { - if (!device.getMemberDevice().isEmpty()) { - return device; + @Nullable CachedBluetoothDeviceManager deviceManager, + @NonNull List devices) { + if (deviceManager == null || devices.isEmpty()) return null; + List cachedDevices = + devices.stream() + .map(device -> deviceManager.findDevice(device)) + .filter(Objects::nonNull) + .collect(toList()); + for (CachedBluetoothDevice cachedDevice : cachedDevices) { + if (!cachedDevice.getMemberDevice().isEmpty()) { + return cachedDevice; } } - CachedBluetoothDevice leadDevice = devices.get(0); + CachedBluetoothDevice leadDevice = cachedDevices.isEmpty() ? null : cachedDevices.get(0); Log.d( TAG, "No lead device in the group, pick arbitrary device as the lead: " - + leadDevice.getDevice().getAnonymizedAddress()); + + (leadDevice == null + ? "null" + : leadDevice.getDevice().getAnonymizedAddress())); return leadDevice; } @@ -206,13 +201,13 @@ public class AudioSharingUtils { @NonNull public static List buildOrderedConnectedLeadAudioSharingDeviceItem( @Nullable LocalBluetoothManager localBtManager, - Map> groupedConnectedDevices, + Map> groupedConnectedDevices, boolean filterByInSharing) { return buildOrderedConnectedLeadDevices( localBtManager, groupedConnectedDevices, filterByInSharing) .stream() .map(AudioSharingUtils::buildAudioSharingDeviceItem) - .collect(Collectors.toList()); + .collect(toList()); } /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ @@ -361,4 +356,27 @@ public class AudioSharingUtils { Pair.create(METRIC_KEY_CANDIDATE_DEVICE_COUNT.ordinal(), candidateDeviceCount) }; } + + private static final Comparator sCachedDeviceComparator = + (CachedBluetoothDevice d1, CachedBluetoothDevice d2) -> { + // Active above not inactive + int comparison = + (isActiveLeAudioDevice(d2) ? 1 : 0) - (isActiveLeAudioDevice(d1) ? 1 : 0); + if (comparison != 0) return comparison; + // Bonded above not bonded + comparison = + (d2.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) + - (d1.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); + if (comparison != 0) return comparison; + // Bond timestamp available above unavailable + comparison = + (d2.getBondTimestamp() != null ? 1 : 0) + - (d1.getBondTimestamp() != null ? 1 : 0); + if (comparison != 0) return comparison; + // Order by bond timestamp if it is available + // Otherwise order by device name + return d1.getBondTimestamp() != null + ? d1.getBondTimestamp().compareTo(d2.getBondTimestamp()) + : d1.getName().compareTo(d2.getName()); + }; } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java index e00a146244d..9c83fa6f8ee 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialogTest.java @@ -23,7 +23,6 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify;