Use LocalBluetoothLeBroadcastSourceState.
Test: atest Bug: 308368124 Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: Ic49a6782b63c91e95dd16898b5443585068e45f0
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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<BluetoothLeBroadcastReceiveState> 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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BluetoothDevice> 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<BluetoothLeBroadcastReceiveState> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BluetoothLeBroadcastReceiveState> 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.
|
||||
*
|
||||
* <p>If multiple sources have the same broadcast ID, the state of the source that is
|
||||
* {@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()
|
||||
.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<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. */
|
||||
@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<BluetoothLeBroadcastReceiveState> 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<BluetoothLeBroadcastReceiveState> 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<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
|
||||
* CachedBluetoothDevice} that's either connected to a broadcast source or is a connected LE
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Integer, AudioStreamPreference> 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<BluetoothLeBroadcastMetadata> 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<BluetoothLeBroadcastMetadata> 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<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> sources =
|
||||
mAudioStreamsHelper.getAllSourcesByDevice();
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> 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<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>>
|
||||
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<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getConnectedSources(
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getStreamSourcesByDevice(
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> 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<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPresentSources(
|
||||
private Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> getPausedSourcesByDevice(
|
||||
Map<BluetoothDevice, List<BluetoothLeBroadcastReceiveState>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user