Use LocalBluetoothLeBroadcastSourceState.

Test: atest
Bug: 308368124
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Change-Id: Ic49a6782b63c91e95dd16898b5443585068e45f0
This commit is contained in:
chelseahao
2025-01-09 17:20:50 +08:00
committed by Chelsea Hao
parent f48f0573e1
commit 47fcd60315
14 changed files with 263 additions and 287 deletions

View File

@@ -82,7 +82,7 @@ public class BluetoothDetailsAudioSharingController extends BluetoothDetailsCont
mProfilesContainer.removeAll(); mProfilesContainer.removeAll();
mProfilesContainer.addPreference(createAudioSharingPreference()); mProfilesContainer.addPreference(createAudioSharingPreference());
if ((BluetoothUtils.isActiveLeAudioDevice(mCachedDevice) if ((BluetoothUtils.isActiveLeAudioDevice(mCachedDevice)
|| AudioStreamsHelper.hasConnectedBroadcastSource( || AudioStreamsHelper.hasBroadcastSource(
mCachedDevice, mLocalBluetoothManager)) mCachedDevice, mLocalBluetoothManager))
&& !BluetoothUtils.isBroadcasting(mLocalBluetoothManager)) { && !BluetoothUtils.isBroadcasting(mLocalBluetoothManager)) {
mProfilesContainer.addPreference(createFindAudioStreamPreference()); mProfilesContainer.addPreference(createFindAudioStreamPreference());

View File

@@ -16,6 +16,10 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -42,7 +46,6 @@ import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.ActionButtonsPreference; import com.android.settingslib.widget.ActionButtonsPreference;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -75,20 +78,19 @@ public class AudioStreamButtonController extends BasePreferenceController
int sourceId, int sourceId,
BluetoothLeBroadcastReceiveState state) { BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state); super.onReceiveStateChanged(sink, sourceId, state);
boolean shouldUpdateButton = var localSourceState = getLocalSourceState(state);
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) boolean shouldUpdateButton = mHysteresisModeFixAvailable
? AudioStreamsHelper.hasSourcePresent(state) ? (localSourceState == PAUSED || localSourceState == STREAMING)
: AudioStreamsHelper.isConnected(state); : localSourceState == STREAMING;
if (shouldUpdateButton) { if (shouldUpdateButton) {
updateButton(); updateButton();
if (AudioStreamsHelper.isConnected(state)) { // TODO(b/308368124): Verify if this log is too noisy.
mMetricsFeatureProvider.action( mMetricsFeatureProvider.action(
mContext, mContext,
SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
SOURCE_ORIGIN_REPOSITORY); SOURCE_ORIGIN_REPOSITORY);
} }
} }
}
@Override @Override
public void onSourceAddFailed( public void onSourceAddFailed(
@@ -113,6 +115,7 @@ public class AudioStreamButtonController extends BasePreferenceController
private final AudioStreamsHelper mAudioStreamsHelper; private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private final MetricsFeatureProvider mMetricsFeatureProvider; private final MetricsFeatureProvider mMetricsFeatureProvider;
private final boolean mHysteresisModeFixAvailable;
private @Nullable ActionButtonsPreference mPreference; private @Nullable ActionButtonsPreference mPreference;
private int mBroadcastId = -1; private int mBroadcastId = -1;
@@ -121,6 +124,8 @@ public class AudioStreamButtonController extends BasePreferenceController
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context)); mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
context);
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
} }
@@ -155,14 +160,8 @@ public class AudioStreamButtonController extends BasePreferenceController
return; return;
} }
List<BluetoothLeBroadcastReceiveState> sources = boolean isConnected = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) mHysteresisModeFixAvailable).containsKey(mBroadcastId);
? mAudioStreamsHelper.getAllPresentSources()
: mAudioStreamsHelper.getAllConnectedSources();
boolean isConnected =
sources.stream()
.map(BluetoothLeBroadcastReceiveState::getBroadcastId)
.anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId);
View.OnClickListener onClickListener; View.OnClickListener onClickListener;

View File

@@ -16,9 +16,10 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static java.util.stream.Collectors.toList; import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -61,6 +62,7 @@ public class AudioStreamHeaderController extends BasePreferenceController
private final Executor mExecutor; private final Executor mExecutor;
private final AudioStreamsHelper mAudioStreamsHelper; private final AudioStreamsHelper mAudioStreamsHelper;
@Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private final boolean mHysteresisModeFixAvailable;
@VisibleForTesting @VisibleForTesting
final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
@@ -83,13 +85,13 @@ public class AudioStreamHeaderController extends BasePreferenceController
int sourceId, int sourceId,
BluetoothLeBroadcastReceiveState state) { BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state); super.onReceiveStateChanged(sink, sourceId, state);
if (AudioStreamsHelper.isConnected(state)) { var localSourceState = getLocalSourceState(state);
if (localSourceState == STREAMING) {
updateSummary(); updateSummary();
mAudioStreamsHelper.startMediaService( mAudioStreamsHelper.startMediaService(
mContext, mBroadcastId, mBroadcastName); mContext, mBroadcastId, mBroadcastName);
} else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) } else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) {
&& AudioStreamsHelper.hasSourcePresent(state)) { // if source paused, only update the summary
// if source present but not connected, only update the summary
updateSummary(); updateSummary();
} }
} }
@@ -105,6 +107,8 @@ public class AudioStreamHeaderController extends BasePreferenceController
mExecutor = Executors.newSingleThreadExecutor(); mExecutor = Executors.newSingleThreadExecutor();
mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context)); mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
context);
} }
@Override @Override
@@ -151,38 +155,9 @@ public class AudioStreamHeaderController extends BasePreferenceController
var unused = var unused =
ThreadUtils.postOnBackgroundThread( ThreadUtils.postOnBackgroundThread(
() -> { () -> {
var connectedSourceList = var sourceState = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
mAudioStreamsHelper.getAllPresentSources().stream() mHysteresisModeFixAvailable).get(mBroadcastId);
.filter( var latestSummary = getLatestSummary(sourceState);
state ->
(state.getBroadcastId()
== mBroadcastId))
.collect(toList());
var latestSummary =
audioSharingHysteresisModeFix()
? connectedSourceList.isEmpty()
? AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY
: (connectedSourceList.stream()
.anyMatch(
AudioStreamsHelper
::isConnected)
? mContext.getString(
AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
: mContext.getString(
AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY))
: mAudioStreamsHelper.getAllConnectedSources().stream()
.map(
BluetoothLeBroadcastReceiveState
::getBroadcastId)
.anyMatch(
connectedBroadcastId ->
connectedBroadcastId
== mBroadcastId)
? mContext.getString(
AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
: AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
ThreadUtils.postOnMainThread( ThreadUtils.postOnMainThread(
() -> { () -> {
if (mHeaderController != null) { if (mHeaderController != null) {
@@ -212,4 +187,16 @@ public class AudioStreamHeaderController extends BasePreferenceController
mBroadcastName = broadcastName; mBroadcastName = broadcastName;
mBroadcastId = broadcastId; mBroadcastId = broadcastId;
} }
private String getLatestSummary(@Nullable LocalBluetoothLeBroadcastSourceState state) {
if (state == null) {
return AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
}
if (mHysteresisModeFixAvailable) {
return state == STREAMING
? mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY)
: mContext.getString(AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY);
}
return mContext.getString(AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY);
}
} }

View File

@@ -16,8 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static java.util.Collections.emptyList;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -25,7 +23,6 @@ import android.app.Service;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothVolumeControl; import android.bluetooth.BluetoothVolumeControl;
import android.content.Intent; import android.content.Intent;
@@ -107,6 +104,7 @@ public class AudioStreamMediaService extends Service {
// override this value. Otherwise, we raise the volume to 25 when the play button is clicked. // override this value. Otherwise, we raise the volume to 25 when the play button is clicked.
private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25); private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25);
private final Object mLocalSessionLock = new Object(); private final Object mLocalSessionLock = new Object();
private boolean mHysteresisModeFixAvailable;
private int mBroadcastId; private int mBroadcastId;
@Nullable private List<BluetoothDevice> mDevices; @Nullable private List<BluetoothDevice> mDevices;
@Nullable private LocalBluetoothManager mLocalBtManager; @Nullable private LocalBluetoothManager mLocalBtManager;
@@ -139,6 +137,7 @@ public class AudioStreamMediaService extends Service {
Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!"); Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!");
return; return;
} }
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this);
mNotificationManager = getSystemService(NotificationManager.class); mNotificationManager = getSystemService(NotificationManager.class);
if (mNotificationManager == null) { if (mNotificationManager == null) {
@@ -309,13 +308,9 @@ public class AudioStreamMediaService extends Service {
} }
private void handleRemoveSource() { private void handleRemoveSource() {
List<BluetoothLeBroadcastReceiveState> connected = if (mAudioStreamsHelper != null
mAudioStreamsHelper == null && !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
? emptyList() mHysteresisModeFixAvailable).containsKey(mBroadcastId)) {
: mAudioStreamsHelper.getAllConnectedSources();
if (connected.stream()
.map(BluetoothLeBroadcastReceiveState::getBroadcastId)
.noneMatch(id -> id == mBroadcastId)) {
stopSelf(); stopSelf();
} }
} }

View File

@@ -19,6 +19,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_ID;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.BROADCAST_TITLE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamMediaService.DEVICES;
import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
@@ -32,6 +38,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -67,12 +74,6 @@ public class AudioStreamsHelper {
private final @Nullable LocalBluetoothManager mBluetoothManager; private final @Nullable LocalBluetoothManager mBluetoothManager;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
// Referring to Broadcast Audio Scan Service 1.0
// Table 3.9: Broadcast Receive State characteristic format
// 0x00000000: 0b0 = Not synchronized to BIS_index[x]
// 0xFFFFFFFF: Failed to sync to BIG
private static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L;
private static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL;
AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) { AudioStreamsHelper(@Nullable LocalBluetoothManager bluetoothManager) {
mBluetoothManager = bluetoothManager; mBluetoothManager = bluetoothManager;
@@ -141,16 +142,31 @@ public class AudioStreamsHelper {
}); });
} }
/** Retrieves a list of all LE broadcast receive states from active sinks. */ /**
public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() { * Gets a map of connected broadcast IDs to their corresponding local broadcast source states.
if (mLeBroadcastAssistant == null) { *
Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!"); * <p>If multiple sources have the same broadcast ID, the state of the source that is
return emptyList(); * {@code STREAMING} is preferred.
*/
public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
boolean hysteresisModeFixAvailable) {
if (mBluetoothManager == null || mLeBroadcastAssistant == null) {
Log.w(TAG,
"getConnectedBroadcastIdAndState(): BluetoothManager or LeBroadcastAssistant "
+ "is null!");
return emptyMap();
} }
return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream() return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream()) .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
.filter(AudioStreamsHelper::isConnected) .map(state -> new Pair<>(state.getBroadcastId(), getLocalSourceState(state)))
.toList(); .filter(pair -> pair.second == STREAMING
|| (hysteresisModeFixAvailable && pair.second == PAUSED))
.collect(toMap(
p -> p.first,
p -> p.second,
(existingState, newState) -> existingState == STREAMING ? existingState
: newState
));
} }
/** Retrieves a list of all LE broadcast receive states keyed by each active device. */ /** Retrieves a list of all LE broadcast receive states keyed by each active device. */
@@ -163,47 +179,12 @@ public class AudioStreamsHelper {
.collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources)); .collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources));
} }
/** Retrieves a list of all LE broadcast receive states from sinks with source present. */
@VisibleForTesting
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "getAllPresentSources(): LeBroadcastAssistant is null!");
return emptyList();
}
return getConnectedBluetoothDevices(mBluetoothManager, /* inSharingOnly= */ true).stream()
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
.filter(AudioStreamsHelper::hasSourcePresent)
.toList();
}
/** Retrieves LocalBluetoothLeBroadcastAssistant. */ /** Retrieves LocalBluetoothLeBroadcastAssistant. */
@Nullable @Nullable
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() { public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
return mLeBroadcastAssistant; return mLeBroadcastAssistant;
} }
/** Checks the connectivity status based on the provided broadcast receive state. */
public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
return state.getBisSyncState().stream()
.anyMatch(
bitmap ->
(bitmap != BIS_SYNC_NOT_SYNC_TO_BIS
&& bitmap != BIS_SYNC_FAILED_SYNC_TO_BIG));
}
/** Checks the connectivity status based on the provided broadcast receive state. */
public static boolean hasSourcePresent(BluetoothLeBroadcastReceiveState state) {
// Referring to Broadcast Audio Scan Service 1.0
// All zero address means no source on the sink device
return !state.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
}
static boolean isBadCode(BluetoothLeBroadcastReceiveState state) {
return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
&& state.getBigEncryptionState()
== BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
}
/** /**
* Returns a {@code CachedBluetoothDevice} that is either connected to a broadcast source or is * Returns a {@code CachedBluetoothDevice} that is either connected to a broadcast source or is
* a connected LE device. * a connected LE device.
@@ -226,7 +207,7 @@ public class AudioStreamsHelper {
} }
var deviceHasSource = var deviceHasSource =
leadDevices.stream() leadDevices.stream()
.filter(device -> hasConnectedBroadcastSource(device, manager)) .filter(device -> hasBroadcastSource(device, manager))
.findFirst(); .findFirst();
if (deviceHasSource.isPresent()) { if (deviceHasSource.isPresent()) {
Log.d( Log.d(
@@ -258,38 +239,38 @@ public class AudioStreamsHelper {
return Optional.empty(); return Optional.empty();
} }
return leadDevices.stream() return leadDevices.stream()
.filter(device -> hasConnectedBroadcastSource(device, manager)) .filter(device -> hasBroadcastSource(device, manager))
.findFirst(); .findFirst();
} }
/** /**
* Check if {@link CachedBluetoothDevice} has connected to a broadcast source. * Check if {@link CachedBluetoothDevice} has a broadcast source that is in STREAMING, PAUSED
* or DECRYPTION_FAILED state.
* *
* @param cachedDevice The cached bluetooth device to check. * @param cachedDevice The cached bluetooth device to check.
* @param localBtManager The BT manager to provide BT functions. * @param localBtManager The BT manager to provide BT functions.
* @return Whether the device has connected to a broadcast source. * @return Whether the device has a broadcast source.
*/ */
public static boolean hasConnectedBroadcastSource( public static boolean hasBroadcastSource(
CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) { CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
if (localBtManager == null) { if (localBtManager == null) {
Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null"); Log.d(TAG, "Skip check hasBroadcastSource due to bt manager is null");
return false; return false;
} }
LocalBluetoothLeBroadcastAssistant assistant = LocalBluetoothLeBroadcastAssistant assistant =
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
if (assistant == null) { if (assistant == null) {
Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null"); Log.d(TAG, "Skip check hasBroadcastSource due to assistant profile is null");
return false; return false;
} }
List<BluetoothLeBroadcastReceiveState> sourceList = List<BluetoothLeBroadcastReceiveState> sourceList =
assistant.getAllSources(cachedDevice.getDevice()); assistant.getAllSources(cachedDevice.getDevice());
if (!sourceList.isEmpty() boolean hysteresisModeFixAvailable = isAudioSharingHysteresisModeFixAvailable(
&& (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( localBtManager.getContext());
localBtManager.getContext()) if (hasReceiveState(sourceList, hysteresisModeFixAvailable)) {
|| sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) {
Log.d( Log.d(
TAG, TAG,
"Lead device has connected broadcast source, device = " "Lead device has broadcast source, device = "
+ cachedDevice.getDevice().getAnonymizedAddress()); + cachedDevice.getDevice().getAnonymizedAddress());
return true; return true;
} }
@@ -297,13 +278,10 @@ public class AudioStreamsHelper {
for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
List<BluetoothLeBroadcastReceiveState> list = List<BluetoothLeBroadcastReceiveState> list =
assistant.getAllSources(device.getDevice()); assistant.getAllSources(device.getDevice());
if (!list.isEmpty() if (hasReceiveState(list, hysteresisModeFixAvailable)) {
&& (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
localBtManager.getContext())
|| list.stream().anyMatch(AudioStreamsHelper::isConnected))) {
Log.d( Log.d(
TAG, TAG,
"Member device has connected broadcast source, device = " "Member device has broadcast source, device = "
+ device.getDevice().getAnonymizedAddress()); + device.getDevice().getAnonymizedAddress());
return true; return true;
} }
@@ -311,6 +289,18 @@ public class AudioStreamsHelper {
return false; return false;
} }
private static boolean hasReceiveState(List<BluetoothLeBroadcastReceiveState> states,
boolean hysteresisModeFixAvailable) {
return states.stream().anyMatch(state -> {
var localSourceState = getLocalSourceState(state);
if (hysteresisModeFixAvailable) {
return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED
|| localSourceState == PAUSED;
}
return localSourceState == STREAMING || localSourceState == DECRYPTION_FAILED;
});
}
/** /**
* Retrieves a list of connected Bluetooth devices that belongs to one {@link * Retrieves a list of connected Bluetooth devices that belongs to one {@link
* CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE * CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE

View File

@@ -16,23 +16,17 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import com.android.settingslib.bluetooth.BluetoothUtils;
public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback { public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
private static final String TAG = "AudioStreamsProgressCategoryCallback";
private final Context mContext;
private final AudioStreamsProgressCategoryController mCategoryController; private final AudioStreamsProgressCategoryController mCategoryController;
public AudioStreamsProgressCategoryCallback( public AudioStreamsProgressCategoryCallback(
Context context,
AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) { AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
mContext = context;
mCategoryController = audioStreamsProgressCategoryController; mCategoryController = audioStreamsProgressCategoryController;
} }
@@ -40,15 +34,11 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA
public void onReceiveStateChanged( public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
super.onReceiveStateChanged(sink, sourceId, state); super.onReceiveStateChanged(sink, sourceId, state);
var sourceState = getLocalSourceState(state);
if (AudioStreamsHelper.isConnected(state)) { switch (sourceState) {
mCategoryController.handleSourceConnected(sink, state); case STREAMING -> mCategoryController.handleSourceStreaming(sink, state);
} else if (AudioStreamsHelper.isBadCode(state)) { case DECRYPTION_FAILED -> mCategoryController.handleSourceConnectBadCode(state);
mCategoryController.handleSourceConnectBadCode(state); case PAUSED -> mCategoryController.handleSourcePaused(sink, state);
} else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)
&& AudioStreamsHelper.hasSourcePresent(state)) {
// Keep this check as the last, source might also present in above states
mCategoryController.handleSourcePresent(sink, state);
} }
} }

View File

@@ -16,6 +16,12 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.bluetooth.BluetoothUtils.isAudioSharingHysteresisModeFixAvailable;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.DECRYPTION_FAILED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
@@ -137,6 +143,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
private final @Nullable LocalBluetoothManager mBluetoothManager; private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap = private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
private final boolean mHysteresisModeFixAvailable;
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode; private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging; private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference; @Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -149,7 +156,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager); mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mMediaControlHelper = new MediaControlHelper(mContext, mBluetoothManager); mMediaControlHelper = new MediaControlHelper(mContext, mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(context, this); mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
mContext);
} }
@Override @Override
@@ -260,7 +269,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// change it's state. // change it's state.
existingPreference.setAudioStreamMetadata(source); existingPreference.setAudioStreamMetadata(source);
if (fromState != AudioStreamState.SOURCE_ADDED if (fromState != AudioStreamState.SOURCE_ADDED
&& (!isAudioSharingHysteresisModeFixAvailable(mContext) && (!mHysteresisModeFixAvailable
|| fromState != AudioStreamState.SOURCE_PRESENT)) { || fromState != AudioStreamState.SOURCE_PRESENT)) {
Log.w( Log.w(
TAG, TAG,
@@ -336,8 +345,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "handleSourceLost()"); Log.d(TAG, "handleSourceLost()");
} }
if (mAudioStreamsHelper.getAllConnectedSources().stream() if (mAudioStreamsHelper.getConnectedBroadcastIdAndState(
.anyMatch(connected -> connected.getBroadcastId() == broadcastId)) { mHysteresisModeFixAvailable).containsKey(broadcastId)) {
Log.d( Log.d(
TAG, TAG,
"handleSourceLost() : keep this preference as the source is still connected."); "handleSourceLost() : keep this preference as the source is still connected.");
@@ -366,14 +375,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// not, means the source is removed from the sink, we move back the preference to SYNCED // not, means the source is removed from the sink, we move back the preference to SYNCED
// state. // state.
if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
|| (isAudioSharingHysteresisModeFixAvailable(mContext) || (mHysteresisModeFixAvailable
&& preference.getAudioStreamState() && preference.getAudioStreamState()
== AudioStreamState.SOURCE_PRESENT)) == AudioStreamState.SOURCE_PRESENT))
&& mAudioStreamsHelper.getAllConnectedSources().stream() && !mAudioStreamsHelper.getConnectedBroadcastIdAndState(
.noneMatch( mHysteresisModeFixAvailable).containsKey(
connected -> preference.getAudioStreamBroadcastId())) {
connected.getBroadcastId()
== preference.getAudioStreamBroadcastId())) {
ThreadUtils.postOnMainThread( ThreadUtils.postOnMainThread(
() -> { () -> {
@@ -395,27 +402,27 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// Expect one of the following: // Expect one of the following:
// 1) No preference existed, create new preference with state SOURCE_ADDED // 1) No preference existed, create new preference with state SOURCE_ADDED
// 2) Any other state, move to SOURCE_ADDED // 2) Any other state, move to SOURCE_ADDED
void handleSourceConnected( void handleSourceStreaming(
BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "handleSourceConnected()"); Log.d(TAG, "handleSourceStreaming()");
} }
if (!AudioStreamsHelper.isConnected(receiveState)) { if (getLocalSourceState(receiveState) != STREAMING) {
return; return;
} }
var broadcastIdConnected = receiveState.getBroadcastId(); var broadcastIdStreaming = receiveState.getBroadcastId();
Optional<BluetoothLeBroadcastMetadata> metadata = Optional<BluetoothLeBroadcastMetadata> metadata =
getMetadataMatchingByBroadcastId( getMetadataMatchingByBroadcastId(
device, receiveState.getSourceId(), broadcastIdConnected); device, receiveState.getSourceId(), broadcastIdStreaming);
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute( mBroadcastIdToPreferenceMap.compute(
broadcastIdConnected, broadcastIdStreaming,
(k, existingPreference) -> { (k, existingPreference) -> {
if (existingPreference == null) { if (existingPreference == null) {
// No existing preference for this source even if it's already connected, // No existing preference for this source even if it's already streaming,
// add one and set initial state to SOURCE_ADDED. This could happen because // add one and set initial state to SOURCE_ADDED. This could happen because
// we retrieves the connected source during onStart() from // we retrieves the streaming source during onStart() from
// AudioStreamsHelper#getAllConnectedSources() even before the source is // AudioStreamsHelper#getAllStreamingSources() even before the source is
// founded by scanning. // founded by scanning.
return metadata.isPresent() return metadata.isPresent()
? addNewPreference( ? addNewPreference(
@@ -440,7 +447,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "handleSourceConnectBadCode()"); Log.d(TAG, "handleSourceConnectBadCode()");
} }
if (!AudioStreamsHelper.isBadCode(receiveState)) { if (getLocalSourceState(receiveState) != DECRYPTION_FAILED) {
return; return;
} }
mBroadcastIdToPreferenceMap.computeIfPresent( mBroadcastIdToPreferenceMap.computeIfPresent(
@@ -467,29 +474,28 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
// Find preference by receiveState and decide next state. // Find preference by receiveState and decide next state.
// Expect one preference existed, move to SOURCE_PRESENT // Expect one preference existed, move to SOURCE_PRESENT
void handleSourcePresent( void handleSourcePaused(
BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) {
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "handleSourcePresent()"); Log.d(TAG, "handleSourcePaused()");
} }
if (!AudioStreamsHelper.hasSourcePresent(receiveState)) { if (!mHysteresisModeFixAvailable || getLocalSourceState(receiveState) != PAUSED) {
return; return;
} }
var broadcastIdConnected = receiveState.getBroadcastId(); var broadcastIdPaused = receiveState.getBroadcastId();
Optional<BluetoothLeBroadcastMetadata> metadata = Optional<BluetoothLeBroadcastMetadata> metadata =
getMetadataMatchingByBroadcastId( getMetadataMatchingByBroadcastId(
device, receiveState.getSourceId(), broadcastIdConnected); device, receiveState.getSourceId(), broadcastIdPaused);
handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState);
mBroadcastIdToPreferenceMap.compute( mBroadcastIdToPreferenceMap.compute(
broadcastIdConnected, broadcastIdPaused,
(k, existingPreference) -> { (k, existingPreference) -> {
if (existingPreference == null) { if (existingPreference == null) {
// No existing preference for this source even if it's already connected, // No existing preference for this source even if it's already existed but
// add one and set initial state to SOURCE_PRESENT. This could happen // currently paused, add one and set initial state to SOURCE_PRESENT. This
// because // could happen because we retrieves the paused source during onStart() from
// we retrieves the connected source during onStart() from // AudioStreamsHelper#getAllPausedSources() even before the source is
// AudioStreamsHelper#getAllPresentSources() even before the source is
// founded by scanning. // founded by scanning.
return metadata.isPresent() return metadata.isPresent()
? addNewPreference( ? addNewPreference(
@@ -580,56 +586,43 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mExecutor.execute( mExecutor.execute(
() -> { () -> {
// Handle QR code scan, display currently connected streams then start scanning // Handle QR code scan, display currently streaming or paused streams then start
// sequentially // scanning sequentially
handleSourceFromQrCodeIfExists(); handleSourceFromQrCodeIfExists();
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources = Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources =
mAudioStreamsHelper.getAllSourcesByDevice(); mAudioStreamsHelper.getAllSourcesByDevice();
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> connectedSources = getStreamSourcesByDevice(sources).forEach(
getConnectedSources(sources);
if (isAudioSharingHysteresisModeFixAvailable(mContext)) {
// With hysteresis mode, we prioritize showing connected sources first.
// If no connected sources are found, we then show present sources.
if (!connectedSources.isEmpty()) {
connectedSources.forEach(
(device, stateList) -> (device, stateList) ->
stateList.forEach( stateList.forEach(
state -> handleSourceConnected(device, state))); state -> handleSourceStreaming(device, state)));
} else { if (mHysteresisModeFixAvailable) {
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPausedSourcesByDevice(sources).forEach(
presentSources = getPresentSources(sources);
presentSources.forEach(
(device, stateList) -> (device, stateList) ->
stateList.forEach( stateList.forEach(
state -> handleSourcePresent(device, state))); state -> handleSourcePaused(device, state)));
}
} else {
connectedSources.forEach(
(device, stateList) ->
stateList.forEach(
state -> handleSourceConnected(device, state)));
} }
mLeBroadcastAssistant.startSearchingForSources(emptyList()); mLeBroadcastAssistant.startSearchingForSources(emptyList());
mMediaControlHelper.start(); mMediaControlHelper.start();
}); });
} }
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getConnectedSources( private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getStreamSourcesByDevice(
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) { Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
return sources.entrySet().stream() return sources.entrySet().stream()
.filter( .filter(
entry -> entry ->
entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected)) entry.getValue().stream().anyMatch(
state -> getLocalSourceState(state) == STREAMING))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
} }
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPresentSources( private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPausedSourcesByDevice(
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) { Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources) {
return sources.entrySet().stream() return sources.entrySet().stream()
.filter( .filter(
entry -> entry ->
entry.getValue().stream() entry.getValue().stream()
.anyMatch(AudioStreamsHelper::hasSourcePresent)) .anyMatch(state -> getLocalSourceState(state) == PAUSED))
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
} }
@@ -742,8 +735,4 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
dialog.dismiss(); dialog.dismiss();
}); });
} }
private static boolean isAudioSharingHysteresisModeFixAvailable(Context context) {
return BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context);
}
} }

View File

@@ -46,6 +46,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow; import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -143,9 +144,12 @@ public class BluetoothDetailsAudioSharingControllerTest extends BluetoothDetails
@Test @Test
@EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
public void connected_hasConnectedBroadcastSource_showTwoPreference() { public void connected_hasBroadcastSource_showTwoPreference() {
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false); when(mCachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
when(mLocalManager when(mLocalManager
.getProfileManager() .getProfileManager()
.getLeAudioBroadcastAssistantProfile() .getLeAudioBroadcastAssistantProfile()

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams; package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX; import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING; import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -71,6 +73,7 @@ import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -164,9 +167,9 @@ public class AudioStreamButtonControllerTest {
} }
@Test @Test
public void testDisplayPreference_sourceConnected_setDisconnectButton() { public void testDisplayPreference_sourceStreaming_setDisconnectButton() {
when(mAudioStreamsHelper.getAllConnectedSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
@@ -190,7 +193,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testDisplayPreference_sourceNotConnected_setConnectButton() { public void testDisplayPreference_sourceNotConnected_setConnectButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.setAudioStreamsRepositoryForTesting(mRepository); mController.setAudioStreamsRepositoryForTesting(mRepository);
var metadataToRejoin = mock(BluetoothLeBroadcastMetadata.class); var metadataToRejoin = mock(BluetoothLeBroadcastMetadata.class);
when(mRepository.getSavedMetadata(any(), anyInt())).thenReturn(metadataToRejoin); when(mRepository.getSavedMetadata(any(), anyInt())).thenReturn(metadataToRejoin);
@@ -216,7 +220,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testCallback_onSourceRemoved_updateButton() { public void testCallback_onSourceRemoved_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoved( mController.mBroadcastAssistantCallback.onSourceRemoved(
@@ -230,9 +235,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testCallback_onSourceRemovedFailed_updateButton() { public void testCallback_onSourceRemovedFailed_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoveFailed( mController.mBroadcastAssistantCallback.onSourceRemoveFailed(
@@ -250,9 +254,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testCallback_onReceiveStateChanged_updateButton() { public void testCallback_onReceiveStateChanged_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L); bisSyncState.add(1L);
@@ -273,7 +276,7 @@ public class AudioStreamButtonControllerTest {
} }
@Test @Test
public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() { public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66"; String address = "11:22:33:44:55:66";
@@ -284,13 +287,16 @@ public class AudioStreamButtonControllerTest {
when(mSourceDevice.getAddress()).thenReturn(address); when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
when(state.getBisSyncState()).thenReturn(bisSyncState); when(state.getBisSyncState()).thenReturn(bisSyncState);
when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(List.of(state)); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Map.of(BROADCAST_ID, PAUSED));
// Create new controller to enable hysteresis mode
mController = new AudioStreamButtonController(mContext, KEY);
mController.init(BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged( mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, state); mock(BluetoothDevice.class), /* sourceId= */ 0, state);
verify(mFeatureFactory.metricsFeatureProvider, never()) verify(mFeatureFactory.metricsFeatureProvider)
.action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt()); .action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt());
// Called twice, once in displayPreference, the other one in callback // Called twice, once in displayPreference, the other one in callback
@@ -302,7 +308,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testCallback_onSourceAddFailed_updateButton() { public void testCallback_onSourceAddFailed_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceAddFailed( mController.mBroadcastAssistantCallback.onSourceAddFailed(
@@ -321,7 +328,8 @@ public class AudioStreamButtonControllerTest {
@Test @Test
public void testCallback_onSourceLost_updateButton() { public void testCallback_onSourceLost_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 0); mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 0);

View File

@@ -19,10 +19,13 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController.AUDIO_STREAM_HEADER_PRESENT_NOW_SUMMARY;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX; import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING; import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
@@ -66,6 +69,7 @@ import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -160,10 +164,9 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
public void testDisplayPreference_sourceConnected_setSummary() { public void testDisplayPreference_sourceStreaming_setSummary() {
when(mAudioStreamsHelper.getAllConnectedSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
@@ -176,8 +179,9 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
public void testDisplayPreference_sourceNotConnected_setSummary() { public void testDisplayPreference_sourceNotStreaming_setSummary() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
@@ -189,18 +193,14 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
public void testDisplayPreference_sourcePresent_setSummary() { public void testDisplayPreference_sourcePaused_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66"; when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Map.of(BROADCAST_ID, PAUSED));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>();
when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
when(mAudioStreamsHelper.getAllPresentSources())
.thenReturn(List.of(mBroadcastReceiveState));
// Create new controller to enable hysteresis mode
mController = new AudioStreamHeaderController(mContext, KEY);
mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
verify(mHeaderController).setLabel(BROADCAST_NAME); verify(mHeaderController).setLabel(BROADCAST_NAME);
@@ -212,10 +212,10 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
public void testDisplayPreference_sourceNotPresent_setSummary() { public void testDisplayPreference_sourceNotPaused_setSummary() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList()); .thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
@@ -228,7 +228,8 @@ public class AudioStreamHeaderControllerTest {
@Test @Test
public void testCallback_onSourceRemoved_updateButton() { public void testCallback_onSourceRemoved_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceRemoved( mController.mBroadcastAssistantCallback.onSourceRemoved(
@@ -241,7 +242,8 @@ public class AudioStreamHeaderControllerTest {
@Test @Test
public void testCallback_onSourceLost_updateButton() { public void testCallback_onSourceLost_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(Collections.emptyMap());
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1); mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1);
@@ -253,8 +255,8 @@ public class AudioStreamHeaderControllerTest {
@Test @Test
public void testCallback_onReceiveStateChanged_updateButton() { public void testCallback_onReceiveStateChanged_updateButton() {
when(mAudioStreamsHelper.getAllConnectedSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, STREAMING));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class);
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
@@ -272,17 +274,20 @@ public class AudioStreamHeaderControllerTest {
} }
@Test @Test
public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() { public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66"; String address = "11:22:33:44:55:66";
when(mAudioStreamsHelper.getAllPresentSources()) when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean()))
.thenReturn(List.of(mBroadcastReceiveState)); .thenReturn(Map.of(BROADCAST_ID, PAUSED));
when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID);
when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice); when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice);
when(mBluetoothDevice.getAddress()).thenReturn(address); when(mBluetoothDevice.getAddress()).thenReturn(address);
// Create new controller to enable hysteresis mode
mController = new AudioStreamHeaderController(mContext, KEY);
mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID);
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
mController.mBroadcastAssistantCallback.onReceiveStateChanged( mController.mBroadcastAssistantCallback.onReceiveStateChanged(
mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState); mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState);

View File

@@ -19,6 +19,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.PAUSED;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX; import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING; import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -149,11 +151,14 @@ public class AudioStreamsHelperTest {
@Test @Test
public void removeSource_noConnectedSource_doNothing() { public void removeSource_noConnectedSource_doNothing() {
String address = "11:22:33:44:55:66";
List<BluetoothDevice> devices = new ArrayList<>(); List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mDevice); devices.add(mDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices); when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class); BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class);
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_2); when(source.getBroadcastId()).thenReturn(BROADCAST_ID_2);
when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address);
when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice); when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice);
when(mCachedDevice.getDevice()).thenReturn(mDevice); when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID); when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID);
@@ -214,15 +219,16 @@ public class AudioStreamsHelperTest {
} }
@Test @Test
public void getAllConnectedSources_noAssistant() { public void getConnectedBroadcastIdAndState_noAssistant() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null); when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
mHelper = new AudioStreamsHelper(mLocalBluetoothManager); mHelper = new AudioStreamsHelper(mLocalBluetoothManager);
assertThat(mHelper.getAllConnectedSources()).isEmpty(); assertThat(mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */
false)).isEmpty();
} }
@Test @Test
public void getAllConnectedSources_returnSource() { public void getConnectedBroadcastIdAndState_returnStreamingSource() {
List<BluetoothDevice> devices = new ArrayList<>(); List<BluetoothDevice> devices = new ArrayList<>();
devices.add(mDevice); devices.add(mDevice);
when(mAssistant.getAllConnectedDevices()).thenReturn(devices); when(mAssistant.getAllConnectedDevices()).thenReturn(devices);
@@ -234,14 +240,15 @@ public class AudioStreamsHelperTest {
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L); bisSyncState.add(1L);
when(source.getBisSyncState()).thenReturn(bisSyncState); when(source.getBisSyncState()).thenReturn(bisSyncState);
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
var list = mHelper.getAllConnectedSources(); var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ false);
assertThat(list).isNotEmpty(); assertThat(map).isNotEmpty();
assertThat(list.get(0)).isEqualTo(source); assertThat(map.get(BROADCAST_ID_1)).isEqualTo(STREAMING);
} }
@Test @Test
public void getAllPresentSources_noSource() { public void getConnectedBroadcastIdAndState_noSource() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
@@ -259,12 +266,12 @@ public class AudioStreamsHelperTest {
when(source.getSourceDevice()).thenReturn(mSourceDevice); when(source.getSourceDevice()).thenReturn(mSourceDevice);
when(mSourceDevice.getAddress()).thenReturn(address); when(mSourceDevice.getAddress()).thenReturn(address);
var list = mHelper.getAllPresentSources(); var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true);
assertThat(list).isEmpty(); assertThat(map).isEmpty();
} }
@Test @Test
public void getAllPresentSources_returnSource() { public void getConnectedBroadcastIdAndState_returnPausedSource() {
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66"; String address = "11:22:33:44:55:66";
@@ -282,10 +289,11 @@ public class AudioStreamsHelperTest {
when(mSourceDevice.getAddress()).thenReturn(address); when(mSourceDevice.getAddress()).thenReturn(address);
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
when(source.getBisSyncState()).thenReturn(bisSyncState); when(source.getBisSyncState()).thenReturn(bisSyncState);
when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1);
var list = mHelper.getAllPresentSources(); var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true);
assertThat(list).isNotEmpty(); assertThat(map).isNotEmpty();
assertThat(list.get(0)).isEqualTo(source); assertThat(map.get(BROADCAST_ID_1)).isEqualTo(PAUSED);
} }
@Test @Test

View File

@@ -77,7 +77,7 @@ public class AudioStreamsProgressCategoryCallbackTest {
BluetoothStatusCodes.FEATURE_SUPPORTED); BluetoothStatusCodes.FEATURE_SUPPORTED);
shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
BluetoothStatusCodes.FEATURE_SUPPORTED); BluetoothStatusCodes.FEATURE_SUPPORTED);
mCallback = new AudioStreamsProgressCategoryCallback(mContext, mController); mCallback = new AudioStreamsProgressCategoryCallback(mController);
} }
@Test @Test
@@ -87,7 +87,7 @@ public class AudioStreamsProgressCategoryCallbackTest {
when(mState.getBisSyncState()).thenReturn(bisSyncState); when(mState.getBisSyncState()).thenReturn(bisSyncState);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
verify(mController).handleSourceConnected(any(), any()); verify(mController).handleSourceStreaming(any(), any());
} }
@Test @Test
@@ -102,7 +102,7 @@ public class AudioStreamsProgressCategoryCallbackTest {
when(mSourceDevice.getAddress()).thenReturn(address); when(mSourceDevice.getAddress()).thenReturn(address);
mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
verify(mController).handleSourcePresent(any(), any()); verify(mController).handleSourcePaused(any(), any());
} }
@Test @Test

View File

@@ -25,6 +25,7 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX; import static com.android.settingslib.flags.Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING; import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
@@ -355,7 +356,7 @@ public class AudioStreamsProgressCategoryControllerTest {
} }
@Test @Test
public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() { public void testOnStart_handleSourceAlreadyStreaming_useNameFromMetadata() {
// Setup a device // Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
@@ -665,8 +666,8 @@ public class AudioStreamsProgressCategoryControllerTest {
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
// A new source found is lost, but the source is still connected // A new source found is lost, but the source is still connected
BluetoothLeBroadcastReceiveState connected = createConnectedMock(NEWLY_FOUND_BROADCAST_ID); when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())).thenReturn(
when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); Map.of(NEWLY_FOUND_BROADCAST_ID, STREAMING));
mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID); mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -819,13 +820,15 @@ public class AudioStreamsProgressCategoryControllerTest {
} }
@Test @Test
public void testHandleSourcePresent_updateState() { public void testHandleSourcePaused_updateState() {
mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
String address = "11:22:33:44:55:66"; String address = "11:22:33:44:55:66";
// Setup a device // Setup a device
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
// Create new controller to enable hysteresis mode
mController = spy(new TestController(mContext, KEY));
// Setup mPreference so it's not null // Setup mPreference so it's not null
mController.displayPreference(mScreen); mController.displayPreference(mScreen);
@@ -844,7 +847,7 @@ public class AudioStreamsProgressCategoryControllerTest {
when(receiveState.getBisSyncState()).thenReturn(bisSyncState); when(receiveState.getBisSyncState()).thenReturn(bisSyncState);
// The new found source is identified as failed to connect // The new found source is identified as failed to connect
mController.handleSourcePresent(mSourceDevice, receiveState); mController.handleSourcePaused(mSourceDevice, receiveState);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
ArgumentCaptor<AudioStreamPreference> preference = ArgumentCaptor<AudioStreamPreference> preference =

View File

@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows; package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -57,8 +59,9 @@ public class ShadowAudioStreamsHelper {
} }
@Implementation @Implementation
public List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() { public Map<Integer, LocalBluetoothLeBroadcastSourceState> getConnectedBroadcastIdAndState(
return sMockHelper.getAllConnectedSources(); boolean hysteresisModeFixAvailable) {
return sMockHelper.getConnectedBroadcastIdAndState(hysteresisModeFixAvailable);
} }
@Implementation @Implementation
@@ -66,11 +69,6 @@ public class ShadowAudioStreamsHelper {
return sMockHelper.getAllSourcesByDevice(); return sMockHelper.getAllSourcesByDevice();
} }
@Implementation
public List<BluetoothLeBroadcastReceiveState> getAllPresentSources() {
return sMockHelper.getAllPresentSources();
}
/** Gets {@link CachedBluetoothDevice} in sharing or le connected */ /** Gets {@link CachedBluetoothDevice} in sharing or le connected */
@Implementation @Implementation
public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected( public static Optional<CachedBluetoothDevice> getCachedBluetoothDeviceInSharingOrLeConnected(