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); } }