diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java index bb4f2a78b47..1e13c2291dd 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java @@ -82,7 +82,7 @@ public class BluetoothDetailsAudioSharingController extends BluetoothDetailsCont mProfilesContainer.removeAll(); mProfilesContainer.addPreference(createAudioSharingPreference()); if ((BluetoothUtils.isActiveLeAudioDevice(mCachedDevice) - || AudioStreamsHelper.hasConnectedBroadcastSource( + || AudioStreamsHelper.hasBroadcastSource( mCachedDevice, mLocalBluetoothManager)) && !BluetoothUtils.isBroadcasting(mLocalBluetoothManager)) { mProfilesContainer.addPreference(createFindAudioStreamPreference()); diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java index f9cce4ce099..b9228a93eac 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java @@ -16,6 +16,10 @@ 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.bluetooth.BluetoothDevice; 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.widget.ActionButtonsPreference; -import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -75,18 +78,17 @@ public class AudioStreamButtonController extends BasePreferenceController int sourceId, BluetoothLeBroadcastReceiveState state) { super.onReceiveStateChanged(sink, sourceId, state); - boolean shouldUpdateButton = - BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) - ? AudioStreamsHelper.hasSourcePresent(state) - : AudioStreamsHelper.isConnected(state); + var localSourceState = getLocalSourceState(state); + boolean shouldUpdateButton = mHysteresisModeFixAvailable + ? (localSourceState == PAUSED || localSourceState == STREAMING) + : localSourceState == STREAMING; if (shouldUpdateButton) { updateButton(); - if (AudioStreamsHelper.isConnected(state)) { - mMetricsFeatureProvider.action( - mContext, - SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, - SOURCE_ORIGIN_REPOSITORY); - } + // TODO(b/308368124): Verify if this log is too noisy. + mMetricsFeatureProvider.action( + mContext, + SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, + SOURCE_ORIGIN_REPOSITORY); } } @@ -113,6 +115,7 @@ public class AudioStreamButtonController extends BasePreferenceController private final AudioStreamsHelper mAudioStreamsHelper; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final MetricsFeatureProvider mMetricsFeatureProvider; + private final boolean mHysteresisModeFixAvailable; private @Nullable ActionButtonsPreference mPreference; private int mBroadcastId = -1; @@ -121,6 +124,8 @@ public class AudioStreamButtonController extends BasePreferenceController mExecutor = Executors.newSingleThreadExecutor(); mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context)); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); + mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( + context); mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } @@ -155,14 +160,8 @@ public class AudioStreamButtonController extends BasePreferenceController return; } - List sources = - BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) - ? mAudioStreamsHelper.getAllPresentSources() - : mAudioStreamsHelper.getAllConnectedSources(); - boolean isConnected = - sources.stream() - .map(BluetoothLeBroadcastReceiveState::getBroadcastId) - .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId); + boolean isConnected = mAudioStreamsHelper.getConnectedBroadcastIdAndState( + mHysteresisModeFixAvailable).containsKey(mBroadcastId); View.OnClickListener onClickListener; diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java index 88efff2b6ab..6037577162f 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java @@ -16,9 +16,10 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; -import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix; - -import static java.util.stream.Collectors.toList; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState; +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.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastAssistant; @@ -61,6 +62,7 @@ public class AudioStreamHeaderController extends BasePreferenceController private final Executor mExecutor; private final AudioStreamsHelper mAudioStreamsHelper; @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; + private final boolean mHysteresisModeFixAvailable; @VisibleForTesting final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = @@ -83,13 +85,13 @@ public class AudioStreamHeaderController extends BasePreferenceController int sourceId, BluetoothLeBroadcastReceiveState state) { super.onReceiveStateChanged(sink, sourceId, state); - if (AudioStreamsHelper.isConnected(state)) { + var localSourceState = getLocalSourceState(state); + if (localSourceState == STREAMING) { updateSummary(); mAudioStreamsHelper.startMediaService( mContext, mBroadcastId, mBroadcastName); - } else if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext) - && AudioStreamsHelper.hasSourcePresent(state)) { - // if source present but not connected, only update the summary + } else if (mHysteresisModeFixAvailable && localSourceState == PAUSED) { + // if source paused, only update the summary updateSummary(); } } @@ -105,6 +107,8 @@ public class AudioStreamHeaderController extends BasePreferenceController mExecutor = Executors.newSingleThreadExecutor(); mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context)); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); + mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( + context); } @Override @@ -151,38 +155,9 @@ public class AudioStreamHeaderController extends BasePreferenceController var unused = ThreadUtils.postOnBackgroundThread( () -> { - var connectedSourceList = - mAudioStreamsHelper.getAllPresentSources().stream() - .filter( - 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; - + var sourceState = mAudioStreamsHelper.getConnectedBroadcastIdAndState( + mHysteresisModeFixAvailable).get(mBroadcastId); + var latestSummary = getLatestSummary(sourceState); ThreadUtils.postOnMainThread( () -> { if (mHeaderController != null) { @@ -212,4 +187,16 @@ public class AudioStreamHeaderController extends BasePreferenceController mBroadcastName = broadcastName; 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); + } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java index 322fd3cad19..50231665865 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java @@ -16,8 +16,6 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; -import static java.util.Collections.emptyList; - import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -25,7 +23,6 @@ import android.app.Service; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothVolumeControl; 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. private final AtomicInteger mLatestPositiveVolume = new AtomicInteger(25); private final Object mLocalSessionLock = new Object(); + private boolean mHysteresisModeFixAvailable; private int mBroadcastId; @Nullable private List mDevices; @Nullable private LocalBluetoothManager mLocalBtManager; @@ -139,6 +137,7 @@ public class AudioStreamMediaService extends Service { Log.w(TAG, "onCreate() : mLeBroadcastAssistant is null!"); return; } + mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(this); mNotificationManager = getSystemService(NotificationManager.class); if (mNotificationManager == null) { @@ -309,13 +308,9 @@ public class AudioStreamMediaService extends Service { } private void handleRemoveSource() { - List connected = - mAudioStreamsHelper == null - ? emptyList() - : mAudioStreamsHelper.getAllConnectedSources(); - if (connected.stream() - .map(BluetoothLeBroadcastReceiveState::getBroadcastId) - .noneMatch(id -> id == mBroadcastId)) { + if (mAudioStreamsHelper != null + && !mAudioStreamsHelper.getConnectedBroadcastIdAndState( + mHysteresisModeFixAvailable).containsKey(mBroadcastId)) { stopSelf(); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java index 4d6c4cab938..0890870442f 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java @@ -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_TITLE; 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.emptyMap; @@ -32,6 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.util.Log; +import android.util.Pair; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; @@ -67,12 +74,6 @@ public class AudioStreamsHelper { private final @Nullable LocalBluetoothManager mBluetoothManager; 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) { mBluetoothManager = bluetoothManager; @@ -141,16 +142,31 @@ public class AudioStreamsHelper { }); } - /** Retrieves a list of all LE broadcast receive states from active sinks. */ - public List getAllConnectedSources() { - if (mLeBroadcastAssistant == null) { - Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!"); - return emptyList(); + /** + * Gets a map of connected broadcast IDs to their corresponding local broadcast source states. + * + *

If multiple sources have the same broadcast ID, the state of the source that is + * {@code STREAMING} is preferred. + */ + public Map 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() .flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream()) - .filter(AudioStreamsHelper::isConnected) - .toList(); + .map(state -> new Pair<>(state.getBroadcastId(), getLocalSourceState(state))) + .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. */ @@ -163,47 +179,12 @@ public class AudioStreamsHelper { .collect(toMap(Function.identity(), mLeBroadcastAssistant::getAllSources)); } - /** Retrieves a list of all LE broadcast receive states from sinks with source present. */ - @VisibleForTesting - public List 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. */ @Nullable public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() { 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 * a connected LE device. @@ -226,7 +207,7 @@ public class AudioStreamsHelper { } var deviceHasSource = leadDevices.stream() - .filter(device -> hasConnectedBroadcastSource(device, manager)) + .filter(device -> hasBroadcastSource(device, manager)) .findFirst(); if (deviceHasSource.isPresent()) { Log.d( @@ -258,38 +239,38 @@ public class AudioStreamsHelper { return Optional.empty(); } return leadDevices.stream() - .filter(device -> hasConnectedBroadcastSource(device, manager)) + .filter(device -> hasBroadcastSource(device, manager)) .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. - * @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) { 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; } LocalBluetoothLeBroadcastAssistant assistant = localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); 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; } List sourceList = assistant.getAllSources(cachedDevice.getDevice()); - if (!sourceList.isEmpty() - && (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( - localBtManager.getContext()) - || sourceList.stream().anyMatch(AudioStreamsHelper::isConnected))) { + boolean hysteresisModeFixAvailable = isAudioSharingHysteresisModeFixAvailable( + localBtManager.getContext()); + if (hasReceiveState(sourceList, hysteresisModeFixAvailable)) { Log.d( TAG, - "Lead device has connected broadcast source, device = " + "Lead device has broadcast source, device = " + cachedDevice.getDevice().getAnonymizedAddress()); return true; } @@ -297,13 +278,10 @@ public class AudioStreamsHelper { for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { List list = assistant.getAllSources(device.getDevice()); - if (!list.isEmpty() - && (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( - localBtManager.getContext()) - || list.stream().anyMatch(AudioStreamsHelper::isConnected))) { + if (hasReceiveState(list, hysteresisModeFixAvailable)) { Log.d( TAG, - "Member device has connected broadcast source, device = " + "Member device has broadcast source, device = " + device.getDevice().getAnonymizedAddress()); return true; } @@ -311,6 +289,18 @@ public class AudioStreamsHelper { return false; } + private static boolean hasReceiveState(List 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 * CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java index 87cea2c1e94..f0d0bebcddb 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java @@ -16,23 +16,17 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState; + import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; -import android.content.Context; - -import com.android.settingslib.bluetooth.BluetoothUtils; public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback { - private static final String TAG = "AudioStreamsProgressCategoryCallback"; - - private final Context mContext; private final AudioStreamsProgressCategoryController mCategoryController; public AudioStreamsProgressCategoryCallback( - Context context, AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) { - mContext = context; mCategoryController = audioStreamsProgressCategoryController; } @@ -40,15 +34,11 @@ public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastA public void onReceiveStateChanged( BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) { super.onReceiveStateChanged(sink, sourceId, state); - - if (AudioStreamsHelper.isConnected(state)) { - mCategoryController.handleSourceConnected(sink, state); - } else if (AudioStreamsHelper.isBadCode(state)) { - mCategoryController.handleSourceConnectBadCode(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); + var sourceState = getLocalSourceState(state); + switch (sourceState) { + case STREAMING -> mCategoryController.handleSourceStreaming(sink, state); + case DECRYPTION_FAILED -> mCategoryController.handleSourceConnectBadCode(state); + case PAUSED -> mCategoryController.handleSourcePaused(sink, state); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java index 6831c5a12e3..24978c6a753 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java @@ -16,6 +16,12 @@ 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.stream.Collectors.toMap; @@ -137,6 +143,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro private final @Nullable LocalBluetoothManager mBluetoothManager; private final ConcurrentHashMap mBroadcastIdToPreferenceMap = new ConcurrentHashMap<>(); + private final boolean mHysteresisModeFixAvailable; private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode; private SourceOriginForLogging mSourceFromQrCodeOriginForLogging; @Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference; @@ -149,7 +156,9 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager); mMediaControlHelper = new MediaControlHelper(mContext, mBluetoothManager); mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); - mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(context, this); + mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this); + mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable( + mContext); } @Override @@ -260,8 +269,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // change it's state. existingPreference.setAudioStreamMetadata(source); if (fromState != AudioStreamState.SOURCE_ADDED - && (!isAudioSharingHysteresisModeFixAvailable(mContext) - || fromState != AudioStreamState.SOURCE_PRESENT)) { + && (!mHysteresisModeFixAvailable + || fromState != AudioStreamState.SOURCE_PRESENT)) { Log.w( TAG, "handleSourceFound(): unexpected state : " @@ -336,8 +345,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro if (DEBUG) { Log.d(TAG, "handleSourceLost()"); } - if (mAudioStreamsHelper.getAllConnectedSources().stream() - .anyMatch(connected -> connected.getBroadcastId() == broadcastId)) { + if (mAudioStreamsHelper.getConnectedBroadcastIdAndState( + mHysteresisModeFixAvailable).containsKey(broadcastId)) { Log.d( TAG, "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 // state. if ((preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED - || (isAudioSharingHysteresisModeFixAvailable(mContext) + || (mHysteresisModeFixAvailable && preference.getAudioStreamState() == AudioStreamState.SOURCE_PRESENT)) - && mAudioStreamsHelper.getAllConnectedSources().stream() - .noneMatch( - connected -> - connected.getBroadcastId() - == preference.getAudioStreamBroadcastId())) { + && !mAudioStreamsHelper.getConnectedBroadcastIdAndState( + mHysteresisModeFixAvailable).containsKey( + preference.getAudioStreamBroadcastId())) { ThreadUtils.postOnMainThread( () -> { @@ -395,27 +402,27 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Expect one of the following: // 1) No preference existed, create new preference with state SOURCE_ADDED // 2) Any other state, move to SOURCE_ADDED - void handleSourceConnected( + void handleSourceStreaming( BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { if (DEBUG) { - Log.d(TAG, "handleSourceConnected()"); + Log.d(TAG, "handleSourceStreaming()"); } - if (!AudioStreamsHelper.isConnected(receiveState)) { + if (getLocalSourceState(receiveState) != STREAMING) { return; } - var broadcastIdConnected = receiveState.getBroadcastId(); + var broadcastIdStreaming = receiveState.getBroadcastId(); Optional metadata = getMetadataMatchingByBroadcastId( - device, receiveState.getSourceId(), broadcastIdConnected); + device, receiveState.getSourceId(), broadcastIdStreaming); handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); mBroadcastIdToPreferenceMap.compute( - broadcastIdConnected, + broadcastIdStreaming, (k, existingPreference) -> { 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 - // we retrieves the connected source during onStart() from - // AudioStreamsHelper#getAllConnectedSources() even before the source is + // we retrieves the streaming source during onStart() from + // AudioStreamsHelper#getAllStreamingSources() even before the source is // founded by scanning. return metadata.isPresent() ? addNewPreference( @@ -440,7 +447,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro if (DEBUG) { Log.d(TAG, "handleSourceConnectBadCode()"); } - if (!AudioStreamsHelper.isBadCode(receiveState)) { + if (getLocalSourceState(receiveState) != DECRYPTION_FAILED) { return; } mBroadcastIdToPreferenceMap.computeIfPresent( @@ -467,29 +474,28 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro // Find preference by receiveState and decide next state. // Expect one preference existed, move to SOURCE_PRESENT - void handleSourcePresent( + void handleSourcePaused( BluetoothDevice device, BluetoothLeBroadcastReceiveState receiveState) { if (DEBUG) { - Log.d(TAG, "handleSourcePresent()"); + Log.d(TAG, "handleSourcePaused()"); } - if (!AudioStreamsHelper.hasSourcePresent(receiveState)) { + if (!mHysteresisModeFixAvailable || getLocalSourceState(receiveState) != PAUSED) { return; } - var broadcastIdConnected = receiveState.getBroadcastId(); + var broadcastIdPaused = receiveState.getBroadcastId(); Optional metadata = getMetadataMatchingByBroadcastId( - device, receiveState.getSourceId(), broadcastIdConnected); + device, receiveState.getSourceId(), broadcastIdPaused); handleQrCodeWithUnsetBroadcastIdIfNeeded(metadata, receiveState); mBroadcastIdToPreferenceMap.compute( - broadcastIdConnected, + broadcastIdPaused, (k, existingPreference) -> { if (existingPreference == null) { - // No existing preference for this source even if it's already connected, - // add one and set initial state to SOURCE_PRESENT. This could happen - // because - // we retrieves the connected source during onStart() from - // AudioStreamsHelper#getAllPresentSources() even before the source is + // No existing preference for this source even if it's already existed but + // currently paused, add one and set initial state to SOURCE_PRESENT. This + // could happen because we retrieves the paused source during onStart() from + // AudioStreamsHelper#getAllPausedSources() even before the source is // founded by scanning. return metadata.isPresent() ? addNewPreference( @@ -580,56 +586,43 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); mExecutor.execute( () -> { - // Handle QR code scan, display currently connected streams then start scanning - // sequentially + // Handle QR code scan, display currently streaming or paused streams then start + // scanning sequentially handleSourceFromQrCodeIfExists(); Map> sources = mAudioStreamsHelper.getAllSourcesByDevice(); - Map> connectedSources = - 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) -> - stateList.forEach( - state -> handleSourceConnected(device, state))); - } else { - Map> - presentSources = getPresentSources(sources); - presentSources.forEach( - (device, stateList) -> - stateList.forEach( - state -> handleSourcePresent(device, state))); - } - } else { - connectedSources.forEach( + getStreamSourcesByDevice(sources).forEach( + (device, stateList) -> + stateList.forEach( + state -> handleSourceStreaming(device, state))); + if (mHysteresisModeFixAvailable) { + getPausedSourcesByDevice(sources).forEach( (device, stateList) -> stateList.forEach( - state -> handleSourceConnected(device, state))); + state -> handleSourcePaused(device, state))); } mLeBroadcastAssistant.startSearchingForSources(emptyList()); mMediaControlHelper.start(); }); } - private Map> getConnectedSources( + private Map> getStreamSourcesByDevice( Map> sources) { return sources.entrySet().stream() .filter( entry -> - entry.getValue().stream().anyMatch(AudioStreamsHelper::isConnected)) + entry.getValue().stream().anyMatch( + state -> getLocalSourceState(state) == STREAMING)) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } - private Map> getPresentSources( + private Map> getPausedSourcesByDevice( Map> sources) { return sources.entrySet().stream() .filter( entry -> entry.getValue().stream() - .anyMatch(AudioStreamsHelper::hasSourcePresent)) + .anyMatch(state -> getLocalSourceState(state) == PAUSED)) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); } @@ -742,8 +735,4 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro dialog.dismiss(); }); } - - private static boolean isAudioSharingHysteresisModeFixAvailable(Context context) { - return BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(context); - } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java index 2ce68e486de..baf95fd4181 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingControllerTest.java @@ -46,6 +46,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; +import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) @@ -143,9 +144,12 @@ public class BluetoothDetailsAudioSharingControllerTest extends BluetoothDetails @Test @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) - public void connected_hasConnectedBroadcastSource_showTwoPreference() { + public void connected_hasBroadcastSource_showTwoPreference() { when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(false); + List bisSyncState = new ArrayList<>(); + bisSyncState.add(1L); + when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState); when(mLocalManager .getProfileManager() .getLeAudioBroadcastAssistantProfile() diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java index 4c25c11db70..6f032097118 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonControllerTest.java @@ -16,6 +16,8 @@ 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_ENABLE_LE_AUDIO_SHARING; @@ -71,6 +73,7 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; @RunWith(RobolectricTestRunner.class) @@ -164,9 +167,9 @@ public class AudioStreamButtonControllerTest { } @Test - public void testDisplayPreference_sourceConnected_setDisconnectButton() { - when(mAudioStreamsHelper.getAllConnectedSources()) - .thenReturn(List.of(mBroadcastReceiveState)); + public void testDisplayPreference_sourceStreaming_setDisconnectButton() { + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, STREAMING)); when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); mController.displayPreference(mScreen); @@ -190,7 +193,8 @@ public class AudioStreamButtonControllerTest { @Test public void testDisplayPreference_sourceNotConnected_setConnectButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.setAudioStreamsRepositoryForTesting(mRepository); var metadataToRejoin = mock(BluetoothLeBroadcastMetadata.class); when(mRepository.getSavedMetadata(any(), anyInt())).thenReturn(metadataToRejoin); @@ -216,7 +220,8 @@ public class AudioStreamButtonControllerTest { @Test public void testCallback_onSourceRemoved_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceRemoved( @@ -230,9 +235,8 @@ public class AudioStreamButtonControllerTest { @Test public void testCallback_onSourceRemovedFailed_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()) - .thenReturn(List.of(mBroadcastReceiveState)); - when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, STREAMING)); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceRemoveFailed( @@ -250,9 +254,8 @@ public class AudioStreamButtonControllerTest { @Test public void testCallback_onReceiveStateChanged_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()) - .thenReturn(List.of(mBroadcastReceiveState)); - when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, STREAMING)); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); List bisSyncState = new ArrayList<>(); bisSyncState.add(1L); @@ -273,7 +276,7 @@ public class AudioStreamButtonControllerTest { } @Test - public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() { + public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() { mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); String address = "11:22:33:44:55:66"; @@ -284,13 +287,16 @@ public class AudioStreamButtonControllerTest { when(mSourceDevice.getAddress()).thenReturn(address); List bisSyncState = new ArrayList<>(); 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.mBroadcastAssistantCallback.onReceiveStateChanged( mock(BluetoothDevice.class), /* sourceId= */ 0, state); - verify(mFeatureFactory.metricsFeatureProvider, never()) + verify(mFeatureFactory.metricsFeatureProvider) .action(any(), eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED), anyInt()); // Called twice, once in displayPreference, the other one in callback @@ -302,7 +308,8 @@ public class AudioStreamButtonControllerTest { @Test public void testCallback_onSourceAddFailed_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceAddFailed( @@ -321,7 +328,8 @@ public class AudioStreamButtonControllerTest { @Test public void testCallback_onSourceLost_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 0); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java index 54b26ec228a..056514620d2 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderControllerTest.java @@ -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_NOT_LISTENING_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_ENABLE_LE_AUDIO_SHARING; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -66,6 +69,7 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; @RunWith(RobolectricTestRunner.class) @@ -160,10 +164,9 @@ public class AudioStreamHeaderControllerTest { } @Test - public void testDisplayPreference_sourceConnected_setSummary() { - when(mAudioStreamsHelper.getAllConnectedSources()) - .thenReturn(List.of(mBroadcastReceiveState)); - when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); + public void testDisplayPreference_sourceStreaming_setSummary() { + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, STREAMING)); mController.displayPreference(mScreen); @@ -176,8 +179,9 @@ public class AudioStreamHeaderControllerTest { } @Test - public void testDisplayPreference_sourceNotConnected_setSummary() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + public void testDisplayPreference_sourceNotStreaming_setSummary() { + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); @@ -189,18 +193,14 @@ public class AudioStreamHeaderControllerTest { } @Test - public void testDisplayPreference_sourcePresent_setSummary() { + public void testDisplayPreference_sourcePaused_setSummary() { mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - String address = "11:22:33:44:55:66"; - - when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); - when(mBroadcastReceiveState.getSourceDevice()).thenReturn(mBluetoothDevice); - when(mBluetoothDevice.getAddress()).thenReturn(address); - List bisSyncState = new ArrayList<>(); - when(mBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState); - when(mAudioStreamsHelper.getAllPresentSources()) - .thenReturn(List.of(mBroadcastReceiveState)); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, PAUSED)); + // Create new controller to enable hysteresis mode + mController = new AudioStreamHeaderController(mContext, KEY); + mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID); mController.displayPreference(mScreen); verify(mHeaderController).setLabel(BROADCAST_NAME); @@ -212,10 +212,10 @@ public class AudioStreamHeaderControllerTest { } @Test - public void testDisplayPreference_sourceNotPresent_setSummary() { + public void testDisplayPreference_sourceNotPaused_setSummary() { mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); - - when(mAudioStreamsHelper.getAllPresentSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); @@ -228,7 +228,8 @@ public class AudioStreamHeaderControllerTest { @Test public void testCallback_onSourceRemoved_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceRemoved( @@ -241,7 +242,8 @@ public class AudioStreamHeaderControllerTest { @Test public void testCallback_onSourceLost_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(Collections.emptyList()); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Collections.emptyMap()); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onSourceLost(/* broadcastId= */ 1); @@ -253,8 +255,8 @@ public class AudioStreamHeaderControllerTest { @Test public void testCallback_onReceiveStateChanged_updateButton() { - when(mAudioStreamsHelper.getAllConnectedSources()) - .thenReturn(List.of(mBroadcastReceiveState)); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())) + .thenReturn(Map.of(BROADCAST_ID, STREAMING)); when(mBroadcastReceiveState.getBroadcastId()).thenReturn(BROADCAST_ID); BluetoothLeBroadcastReceiveState state = mock(BluetoothLeBroadcastReceiveState.class); List bisSyncState = new ArrayList<>(); @@ -272,17 +274,20 @@ public class AudioStreamHeaderControllerTest { } @Test - public void testCallback_onReceiveStateChangedWithSourcePresent_updateButton() { + public void testCallback_onReceiveStateChangedWithSourcePaused_updateButton() { mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); String address = "11:22:33:44:55:66"; - when(mAudioStreamsHelper.getAllPresentSources()) - .thenReturn(List.of(mBroadcastReceiveState)); + 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); + // Create new controller to enable hysteresis mode + mController = new AudioStreamHeaderController(mContext, KEY); + mController.init(mFragment, BROADCAST_NAME, BROADCAST_ID); mController.displayPreference(mScreen); mController.mBroadcastAssistantCallback.onReceiveStateChanged( mock(BluetoothDevice.class), /* sourceId= */ 0, mBroadcastReceiveState); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java index abfc4b7cc80..ba37c83055a 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelperTest.java @@ -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_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_ENABLE_LE_AUDIO_SHARING; @@ -149,11 +151,14 @@ public class AudioStreamsHelperTest { @Test public void removeSource_noConnectedSource_doNothing() { + String address = "11:22:33:44:55:66"; List devices = new ArrayList<>(); devices.add(mDevice); when(mAssistant.getAllConnectedDevices()).thenReturn(devices); BluetoothLeBroadcastReceiveState source = mock(BluetoothLeBroadcastReceiveState.class); when(source.getBroadcastId()).thenReturn(BROADCAST_ID_2); + when(source.getSourceDevice()).thenReturn(mSourceDevice); + when(mSourceDevice.getAddress()).thenReturn(address); when(mDeviceManager.findDevice(any())).thenReturn(mCachedDevice); when(mCachedDevice.getDevice()).thenReturn(mDevice); when(mCachedDevice.getGroupId()).thenReturn(GROUP_ID); @@ -214,15 +219,16 @@ public class AudioStreamsHelperTest { } @Test - public void getAllConnectedSources_noAssistant() { + public void getConnectedBroadcastIdAndState_noAssistant() { when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null); mHelper = new AudioStreamsHelper(mLocalBluetoothManager); - assertThat(mHelper.getAllConnectedSources()).isEmpty(); + assertThat(mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ + false)).isEmpty(); } @Test - public void getAllConnectedSources_returnSource() { + public void getConnectedBroadcastIdAndState_returnStreamingSource() { List devices = new ArrayList<>(); devices.add(mDevice); when(mAssistant.getAllConnectedDevices()).thenReturn(devices); @@ -234,14 +240,15 @@ public class AudioStreamsHelperTest { List bisSyncState = new ArrayList<>(); bisSyncState.add(1L); when(source.getBisSyncState()).thenReturn(bisSyncState); + when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1); - var list = mHelper.getAllConnectedSources(); - assertThat(list).isNotEmpty(); - assertThat(list.get(0)).isEqualTo(source); + var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ false); + assertThat(map).isNotEmpty(); + assertThat(map.get(BROADCAST_ID_1)).isEqualTo(STREAMING); } @Test - public void getAllPresentSources_noSource() { + public void getConnectedBroadcastIdAndState_noSource() { mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); @@ -259,12 +266,12 @@ public class AudioStreamsHelperTest { when(source.getSourceDevice()).thenReturn(mSourceDevice); when(mSourceDevice.getAddress()).thenReturn(address); - var list = mHelper.getAllPresentSources(); - assertThat(list).isEmpty(); + var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true); + assertThat(map).isEmpty(); } @Test - public void getAllPresentSources_returnSource() { + public void getConnectedBroadcastIdAndState_returnPausedSource() { mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING); mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); String address = "11:22:33:44:55:66"; @@ -282,10 +289,11 @@ public class AudioStreamsHelperTest { when(mSourceDevice.getAddress()).thenReturn(address); List bisSyncState = new ArrayList<>(); when(source.getBisSyncState()).thenReturn(bisSyncState); + when(source.getBroadcastId()).thenReturn(BROADCAST_ID_1); - var list = mHelper.getAllPresentSources(); - assertThat(list).isNotEmpty(); - assertThat(list.get(0)).isEqualTo(source); + var map = mHelper.getConnectedBroadcastIdAndState(/* hysteresisModeFixAvailable= */ true); + assertThat(map).isNotEmpty(); + assertThat(map.get(BROADCAST_ID_1)).isEqualTo(PAUSED); } @Test diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java index 6aff8c38d7e..199284657e3 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java @@ -77,7 +77,7 @@ public class AudioStreamsProgressCategoryCallbackTest { BluetoothStatusCodes.FEATURE_SUPPORTED); shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported( BluetoothStatusCodes.FEATURE_SUPPORTED); - mCallback = new AudioStreamsProgressCategoryCallback(mContext, mController); + mCallback = new AudioStreamsProgressCategoryCallback(mController); } @Test @@ -87,7 +87,7 @@ public class AudioStreamsProgressCategoryCallbackTest { when(mState.getBisSyncState()).thenReturn(bisSyncState); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); - verify(mController).handleSourceConnected(any(), any()); + verify(mController).handleSourceStreaming(any(), any()); } @Test @@ -102,7 +102,7 @@ public class AudioStreamsProgressCategoryCallbackTest { when(mSourceDevice.getAddress()).thenReturn(address); mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState); - verify(mController).handleSourcePresent(any(), any()); + verify(mController).handleSourcePaused(any(), any()); } @Test diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java index f042329200a..8cccbd443f1 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java @@ -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.UNSET_BROADCAST_ID; 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_ENABLE_LE_AUDIO_SHARING; @@ -355,7 +356,7 @@ public class AudioStreamsProgressCategoryControllerTest { } @Test - public void testOnStart_handleSourceAlreadyConnected_useNameFromMetadata() { + public void testOnStart_handleSourceAlreadyStreaming_useNameFromMetadata() { // Setup a device ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); @@ -665,8 +666,8 @@ public class AudioStreamsProgressCategoryControllerTest { shadowOf(Looper.getMainLooper()).idle(); // A new source found is lost, but the source is still connected - BluetoothLeBroadcastReceiveState connected = createConnectedMock(NEWLY_FOUND_BROADCAST_ID); - when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected)); + when(mAudioStreamsHelper.getConnectedBroadcastIdAndState(anyBoolean())).thenReturn( + Map.of(NEWLY_FOUND_BROADCAST_ID, STREAMING)); mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID); shadowOf(Looper.getMainLooper()).idle(); @@ -819,13 +820,15 @@ public class AudioStreamsProgressCategoryControllerTest { } @Test - public void testHandleSourcePresent_updateState() { + public void testHandleSourcePaused_updateState() { mSetFlagsRule.enableFlags(FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX); String address = "11:22:33:44:55:66"; // Setup a device ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice); + // Create new controller to enable hysteresis mode + mController = spy(new TestController(mContext, KEY)); // Setup mPreference so it's not null mController.displayPreference(mScreen); @@ -844,7 +847,7 @@ public class AudioStreamsProgressCategoryControllerTest { when(receiveState.getBisSyncState()).thenReturn(bisSyncState); // The new found source is identified as failed to connect - mController.handleSourcePresent(mSourceDevice, receiveState); + mController.handleSourcePaused(mSourceDevice, receiveState); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor preference = diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java index e5e51fce717..1d3c7a0ddd6 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java @@ -16,6 +16,8 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows; +import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState; + import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; @@ -57,8 +59,9 @@ public class ShadowAudioStreamsHelper { } @Implementation - public List getAllConnectedSources() { - return sMockHelper.getAllConnectedSources(); + public Map getConnectedBroadcastIdAndState( + boolean hysteresisModeFixAvailable) { + return sMockHelper.getConnectedBroadcastIdAndState(hysteresisModeFixAvailable); } @Implementation @@ -66,11 +69,6 @@ public class ShadowAudioStreamsHelper { return sMockHelper.getAllSourcesByDevice(); } - @Implementation - public List getAllPresentSources() { - return sMockHelper.getAllPresentSources(); - } - /** Gets {@link CachedBluetoothDevice} in sharing or le connected */ @Implementation public static Optional getCachedBluetoothDeviceInSharingOrLeConnected(