Loading src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java +1 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java +18 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -75,20 +78,19 @@ 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)) { // TODO(b/308368124): Verify if this log is too noisy. mMetricsFeatureProvider.action( mContext, SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, SOURCE_ORIGIN_REPOSITORY); } } } @Override public void onSourceAddFailed( Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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; Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java +26 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading @@ -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(); } } Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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); } } src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +5 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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(); } } Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +56 −66 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading @@ -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. Loading @@ -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( Loading Loading @@ -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 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; } Loading @@ -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; } Loading @@ -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 Loading Loading
src/com/android/settings/bluetooth/BluetoothDetailsAudioSharingController.java +1 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java +18 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -75,20 +78,19 @@ 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)) { // TODO(b/308368124): Verify if this log is too noisy. mMetricsFeatureProvider.action( mContext, SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED, SOURCE_ORIGIN_REPOSITORY); } } } @Override public void onSourceAddFailed( Loading @@ -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; Loading @@ -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(); } Loading Loading @@ -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; Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java +26 −39 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = Loading @@ -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(); } } Loading @@ -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 Loading Loading @@ -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) { Loading Loading @@ -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); } }
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamMediaService.java +5 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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(); } } Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java +56 −66 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading @@ -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. Loading @@ -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( Loading Loading @@ -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 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; } Loading @@ -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; } Loading @@ -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 Loading