Merge "[Audiosharing] Sort sharing candidates in Utils." into main

This commit is contained in:
Yiyi Shen
2023-11-21 12:23:59 +00:00
committed by Android (Google) Code Review
3 changed files with 175 additions and 43 deletions

View File

@@ -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<AudioSharingDeviceItem> 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) {

View File

@@ -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<AudioSharingDeviceItem> buildOrderedDeviceItemsInSharingSession(
public static ArrayList<CachedBluetoothDevice> buildOrderedConnectedLeadDevices(
LocalBluetoothManager localBtManager,
Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices,
LocalBluetoothManager localBtManager) {
ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
boolean filterByInSharing) {
ArrayList<CachedBluetoothDevice> orderedDevices = new ArrayList<>();
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) return deviceItems;
CachedBluetoothDevice activeDevice = null;
List<CachedBluetoothDevice> inactiveDevices = new ArrayList<>();
if (assistant == null) return orderedDevices;
for (List<CachedBluetoothDevice> devices : groupedConnectedDevices.values()) {
CachedBluetoothDevice leadDevice = null;
for (CachedBluetoothDevice device : devices) {
List<BluetoothLeBroadcastReceiveState> 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<AudioSharingDeviceItem> buildOrderedConnectedLeadAudioSharingDeviceItem(
LocalBluetoothManager localBtManager,
Map<Integer, List<CachedBluetoothDevice>> 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<BluetoothLeBroadcastReceiveState> sourceList =
assistant.getAllSources(cachedDevice.getDevice());
if (!sourceList.isEmpty()) return true;
// Return true if member device is in broadcast.
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
List<BluetoothLeBroadcastReceiveState> 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());
}
}

View File

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