From 1e71a03e65fc5389f63db80f207469dab3ca0b6b Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Tue, 21 Nov 2023 15:24:29 +0800 Subject: [PATCH] [Audiosharing] Sort sharing candidates in Utils. The active device is placed before the inactive one. The bonded device is placed before the unbonded one. The device with earlier bond timestamp is placed before the later one. Otherwise, the device is ordered by name. Flagged with enable_le_audio_sharing Bug: 305620450 Test: Manual Change-Id: Ie1eea7361fbed1635313b92790cec034cf669bbf --- ...udioSharingDevicePreferenceController.java | 21 ++- .../audiosharing/AudioSharingUtils.java | 169 ++++++++++++++---- .../CallsAndAlarmsPreferenceController.java | 28 +-- 3 files changed, 175 insertions(+), 43 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java index 0d2b53add08..1855667fbea 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java @@ -49,6 +49,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -174,6 +175,13 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro + source + ", reason = " + reason); + AudioSharingUtils.toastMessage( + mContext, + String.format( + Locale.US, + "Fail to add source to %s reason %d", + sink.getAddress(), + reason)); } @Override @@ -209,6 +217,13 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro + sourceId + ", reason = " + reason); + AudioSharingUtils.toastMessage( + mContext, + String.format( + Locale.US, + "Fail to remove source from %s reason %d", + sink.getAddress(), + reason)); } @Override @@ -284,7 +299,7 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro mPreferenceGroup.setVisible(false); mAudioSharingSettingsPreference.setVisible(false); - if (isAvailable()) { + if (isAvailable() && mBluetoothDeviceUpdater != null) { mBluetoothDeviceUpdater.setPrefContext(screen.getContext()); mBluetoothDeviceUpdater.forceUpdate(); } @@ -379,8 +394,8 @@ public class AudioSharingDevicePreferenceController extends BasePreferenceContro // Show audio sharing switch or join dialog according to device count in the sharing // session. ArrayList deviceItemsInSharingSession = - AudioSharingUtils.buildOrderedDeviceItemsInSharingSession( - groupedDevices, mLocalBtManager); + AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( + mLocalBtManager, groupedDevices, /* filterByInSharing= */ true); // Show audio sharing switch dialog when the third eligible (LE audio) remote device // connected during a sharing session. if (deviceItemsInSharingSession.size() >= 2) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index a0d44ff18b9..53e095b6f9d 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -19,18 +19,22 @@ package com.android.settings.connecteddevice.audiosharing; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; +import android.content.Context; import android.util.Log; +import android.widget.Toast; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class AudioSharingUtils { private static final String TAG = "AudioSharingUtils"; @@ -73,48 +77,102 @@ public class AudioSharingUtils { } /** - * Fetch a list of {@link AudioSharingDeviceItem}s in the audio sharing session. + * Fetch a list of ordered connected lead {@link CachedBluetoothDevice}s eligible for audio + * sharing. The active device is placed in the first place if it exists. The devices can be + * filtered by whether it is already in the audio sharing session. * + * @param localBtManager The BT manager to provide BT functions. * * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group * id. - * @param localBtManager The BT manager to provide BT functions. - * @return A list of connected devices in the audio sharing session. + * @param filterByInSharing Whether to filter the device by if is already in the sharing + * session. + * @return A list of ordered connected devices eligible for the audio sharing. The active device + * is placed in the first place if it exists. */ - public static ArrayList buildOrderedDeviceItemsInSharingSession( + public static ArrayList buildOrderedConnectedLeadDevices( + LocalBluetoothManager localBtManager, Map> groupedConnectedDevices, - LocalBluetoothManager localBtManager) { - ArrayList deviceItems = new ArrayList<>(); + boolean filterByInSharing) { + ArrayList orderedDevices = new ArrayList<>(); LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); - if (assistant == null) return deviceItems; - CachedBluetoothDevice activeDevice = null; - List inactiveDevices = new ArrayList<>(); + if (assistant == null) return orderedDevices; for (List devices : groupedConnectedDevices.values()) { + CachedBluetoothDevice leadDevice = null; for (CachedBluetoothDevice device : devices) { - List sourceList = - assistant.getAllSources(device.getDevice()); - if (!sourceList.isEmpty()) { - // Use random device in the group within the sharing session to - // represent the group. - if (BluetoothUtils.isActiveLeAudioDevice(device)) { - activeDevice = device; - } else { - inactiveDevices.add(device); - } + if (!device.getMemberDevice().isEmpty()) { + leadDevice = device; break; } } + if (leadDevice == null && !devices.isEmpty()) { + leadDevice = devices.get(0); + Log.d( + TAG, + "Empty member device, pick arbitrary device as the lead: " + + leadDevice.getDevice().getAnonymizedAddress()); + } + if (leadDevice == null) { + Log.d(TAG, "Skip due to no lead device"); + continue; + } + if (filterByInSharing && !hasBroadcastSource(leadDevice, localBtManager)) { + Log.d( + TAG, + "Filtered the device due to not in sharing session: " + + leadDevice.getDevice().getAnonymizedAddress()); + continue; + } + orderedDevices.add(leadDevice); } - if (activeDevice != null) { - deviceItems.add(buildAudioSharingDeviceItem(activeDevice)); - } - inactiveDevices.stream() - .sorted(CachedBluetoothDevice::compareTo) - .forEach( - device -> { - deviceItems.add(buildAudioSharingDeviceItem(device)); - }); - return deviceItems; + 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()); + }); + return orderedDevices; + } + + /** + * Fetch a list of ordered connected lead {@link AudioSharingDeviceItem}s eligible for audio + * sharing. The active device is placed in the first place if it exists. The devices can be + * filtered by whether it is already in the audio sharing session. + * + * @param localBtManager The BT manager to provide BT functions. * + * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group + * id. + * @param filterByInSharing Whether to filter the device by if is already in the sharing + * session. + * @return A list of ordered connected devices eligible for the audio sharing. The active device + * is placed in the first place if it exists. + */ + public static ArrayList buildOrderedConnectedLeadAudioSharingDeviceItem( + LocalBluetoothManager localBtManager, + Map> groupedConnectedDevices, + boolean filterByInSharing) { + return buildOrderedConnectedLeadDevices( + localBtManager, groupedConnectedDevices, filterByInSharing) + .stream() + .map(device -> buildAudioSharingDeviceItem(device)) + .collect(Collectors.toCollection(ArrayList::new)); } /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ @@ -123,6 +181,57 @@ public class AudioSharingUtils { return new AudioSharingDeviceItem( cachedDevice.getName(), cachedDevice.getGroupId(), - BluetoothUtils.isActiveLeAudioDevice(cachedDevice)); + isActiveLeAudioDevice(cachedDevice)); + } + + /** + * Check if {@link CachedBluetoothDevice} is in an audio sharing session. + * + * @param cachedDevice The cached bluetooth device to check. + * @param localBtManager The BT manager to provide BT functions. + * @return Whether the device is in an audio sharing session. + */ + public static boolean hasBroadcastSource( + CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { + LocalBluetoothLeBroadcastAssistant assistant = + localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); + if (assistant == null) { + return false; + } + List sourceList = + assistant.getAllSources(cachedDevice.getDevice()); + if (!sourceList.isEmpty()) return true; + // Return true if member device is in broadcast. + for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { + List list = + assistant.getAllSources(device.getDevice()); + if (!list.isEmpty()) return true; + } + return false; + } + + /** + * Check if {@link CachedBluetoothDevice} is an active le audio device. + * + * @param cachedDevice The cached bluetooth device to check. + * @return Whether the device is an active le audio device. + */ + public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) { + if (BluetoothUtils.isActiveLeAudioDevice(cachedDevice)) { + return true; + } + // Return true if member device is an active le audio device. + for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { + if (BluetoothUtils.isActiveLeAudioDevice(device)) { + return true; + } + } + return false; + } + + /** Toast message on main thread. */ + public static void toastMessage(Context context, String message) { + ThreadUtils.postOnMainThread( + () -> Toast.makeText(context, message, Toast.LENGTH_LONG).show()); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java index a7d18e76415..a6adf8a2cd6 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java @@ -62,14 +62,6 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - updateDeviceItemsInSharingSession(); - // mDeviceItemsInSharingSession is ordered. The active device is the first place if exits. - if (!mDeviceItemsInSharingSession.isEmpty() - && mDeviceItemsInSharingSession.get(0).isActive()) { - mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName()); - } else { - mPreference.setSummary(""); - } mPreference.setOnPreferenceClickListener( preference -> { if (mFragment == null) { @@ -106,6 +98,22 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen } } + @Override + public void updateVisibility(boolean isVisible) { + super.updateVisibility(isVisible); + if (isVisible && mPreference != null) { + updateDeviceItemsInSharingSession(); + // mDeviceItemsInSharingSession is ordered. The active device is the first place if + // exits. + if (!mDeviceItemsInSharingSession.isEmpty() + && mDeviceItemsInSharingSession.get(0).isActive()) { + mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName()); + } else { + mPreference.setSummary(""); + } + } + } + @Override public void onActiveDeviceChanged( @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { @@ -129,7 +137,7 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen mGroupedConnectedDevices = AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); mDeviceItemsInSharingSession = - AudioSharingUtils.buildOrderedDeviceItemsInSharingSession( - mGroupedConnectedDevices, mLocalBtManager); + AudioSharingUtils.buildOrderedConnectedLeadAudioSharingDeviceItem( + mLocalBtManager, mGroupedConnectedDevices, /* filterByInSharing= */ true); } }