Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 97ccf7d6 authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge "Use `LocalBluetoothLeBroadcastSourceState`." into main

parents f597576f 47fcd603
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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());
+18 −19
Original line number Diff line number Diff line
@@ -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,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(
@@ -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;

+26 −39
Original line number Diff line number Diff line
@@ -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);
    }
}
+5 −10
Original line number Diff line number Diff line
@@ -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();
            }
        }
+56 −66
Original line number Diff line number Diff line
@@ -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 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
Loading