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

Commit fc1985f4 authored by chelseahao's avatar chelseahao
Browse files

Add util functions to get and modify channel selection of BluetoothLeBroadcastMetadata.

Flag: com.android.settingslib.flags.audio_stream_play_pause_by_modify_source
Test: atest
Bug: 384976631
Change-Id: I777cccb66be76efe13f0a6111d634a7d7daf6785
parent 9a50f33c
Loading
Loading
Loading
Loading
+101 −3
Original line number Diff line number Diff line
package com.android.settingslib.bluetooth;

import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.UNKNOWN_CHANNEL;
import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;

import static java.util.stream.Collectors.toSet;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastChannel;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
@@ -38,6 +44,7 @@ import android.util.Pair;
import android.view.InputDevice;

import androidx.annotation.DrawableRes;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -53,6 +60,8 @@ import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -60,7 +69,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class BluetoothUtils {
    private static final String TAG = "BluetoothUtils";
@@ -652,13 +660,13 @@ public class BluetoothUtils {
                        .map(deviceManager::findDevice)
                        .filter(Objects::nonNull)
                        .map(BluetoothUtils::getGroupId)
                        .collect(Collectors.toSet());
                        .collect(toSet());
        Set<Integer> activeGroupIds =
                leAudioProfile.getActiveDevices().stream()
                        .map(deviceManager::findDevice)
                        .filter(Objects::nonNull)
                        .map(BluetoothUtils::getGroupId)
                        .collect(Collectors.toSet());
                        .collect(toSet());
        int groupId = getGroupId(cachedDevice);
        return activeGroupIds.size() == 1
                && !activeGroupIds.contains(groupId)
@@ -1305,4 +1313,94 @@ public class BluetoothUtils {
            return null;
        }
    }

    /**
     * Gets the index of the first selected channel for the first subgroup for a given broadcast
     * source Id on a Bluetooth sink device.
     */
    @NonNull
    public static Set<Integer> getSelectedChannelIndex(
            @NonNull LocalBluetoothProfileManager profileManager,
            @NonNull BluetoothDevice sink, @IntRange(from = 0x00, to = 0xFF) int sourceId) {
        LocalBluetoothLeBroadcastAssistant assistant =
                profileManager.getLeAudioBroadcastAssistantProfile();
        if (assistant == null) {
            Log.w(TAG, "getSelectedChannelIndex(): assistant is null");
            return UNKNOWN_CHANNEL;
        }
        BluetoothLeBroadcastMetadata metadata = assistant.getSourceMetadata(sink, sourceId);
        if (metadata == null) {
            Log.w(TAG, "getSelectedChannelIndex(): metadata is null");
            return UNKNOWN_CHANNEL;
        }
        List<BluetoothLeBroadcastSubgroup> subgroups = metadata.getSubgroups();
        if (subgroups == null || subgroups.isEmpty()) {
            Log.d(TAG, "getSelectedChannelIndex(): subgroup is null or empty");
            return UNKNOWN_CHANNEL;
        }
        Set<Integer> selectedChannels =
                subgroups.getFirst().getChannels().stream().filter(
                        BluetoothLeBroadcastChannel::isSelected).map(
                        BluetoothLeBroadcastChannel::getChannelIndex).collect(
                        toSet());
        if (selectedChannels == null || selectedChannels.isEmpty()) {
            Log.d(TAG, "getSelectedChannelIndex(): selected channel is null or empty");
            return UNKNOWN_CHANNEL;
        }
        return new HashSet<>(selectedChannels);
    }

    /**
     * Sets the selected state of a specific channel index for a given broadcast source Id on a
     * Bluetooth sink device by modifying the broadcast metadata. This method assumes the channel
     * belongs to the first subgroup in the metadata.
     */
    public static void modifySelectedChannelIndex(
            @NonNull LocalBluetoothProfileManager profileManager,
            @NonNull BluetoothDevice sink, @IntRange(from = 0x00, to = 0xFF) int sourceId,
            @NonNull Set<Integer> channelIndex, boolean shouldSelect) {
        LocalBluetoothLeBroadcastAssistant assistant =
                profileManager.getLeAudioBroadcastAssistantProfile();
        if (assistant == null) {
            Log.w(TAG, "modifySelectedChannelIndex(): assistant is null");
            return;
        }
        BluetoothLeBroadcastMetadata original = assistant.getSourceMetadata(sink, sourceId);
        if (original == null) {
            Log.w(TAG, "modifySelectedChannelIndex(): metadata is null");
            return;
        }
        List<BluetoothLeBroadcastSubgroup> subgroups = new ArrayList<>(original.getSubgroups());
        if (subgroups == null || subgroups.isEmpty()) {
            Log.d(TAG, "modifySelectedChannelIndex(): subgroup is null or empty");
            return;
        }
        BluetoothLeBroadcastSubgroup firstSubgroup = subgroups.getFirst();
        List<BluetoothLeBroadcastChannel> channels = firstSubgroup.getChannels();
        if (!channels.stream().map(BluetoothLeBroadcastChannel::getChannelIndex).collect(
                toSet()).containsAll(channelIndex)) {
            Log.d(TAG, "modifySelectedChannelIndex(): no channel found for given index");
            return;
        }
        List<BluetoothLeBroadcastChannel> updatedChannels = channels.stream()
                .map(c ->
                        channelIndex.contains(c.getChannelIndex()) && c.isSelected() != shouldSelect
                        ? new BluetoothLeBroadcastChannel.Builder(c).setSelected(
                        shouldSelect).build() : c).toList();
        if (updatedChannels.equals(channels)) {
            Log.d(TAG, "modifySelectedChannelIndex(): no change needed");
            return;
        }
        BluetoothLeBroadcastSubgroup.Builder updatedSubgroupBuilder =
                new BluetoothLeBroadcastSubgroup.Builder(firstSubgroup);
        updatedSubgroupBuilder.clearChannel();
        updatedChannels.forEach(updatedSubgroupBuilder::addChannel);
        subgroups.set(0, updatedSubgroupBuilder.build());
        var updatedBuilder = new BluetoothLeBroadcastMetadata.Builder(original).clearSubgroup();
        subgroups.forEach(updatedBuilder::addSubgroup);
        BluetoothLeBroadcastMetadata updated = updatedBuilder.build();
        Log.d(TAG, "modifySelectedChannelIndex(): existedMetadata = " + original
                + " updatedMetadata = " + updated);
        assistant.modifySource(sink, sourceId, updated);
    }
}
+34 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.util.Pair;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
@@ -38,10 +39,13 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.android.settingslib.R;
import com.android.settingslib.flags.Flags;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -52,12 +56,15 @@ import java.util.concurrent.Executors;
 * BluetoothLeBroadcastAssistant.Callback} to get the result callback.
 */
public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile {
    public static final HashSet<Integer> UNKNOWN_CHANNEL = new HashSet<>(List.of(-1));

    /** A derived source state based on {@link BluetoothLeBroadcastReceiveState}. */
    public enum LocalBluetoothLeBroadcastSourceState {
        UNKNOWN,
        STREAMING,
        DECRYPTION_FAILED,
        PAUSED,
        PAUSED_BY_RECEIVER;
    }

    private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
@@ -620,4 +627,31 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile
        }
        return LocalBluetoothLeBroadcastSourceState.UNKNOWN;
    }

    /**
     * Returns the source connection status with channel selected based on the provided broadcast
     * receive state and source metadata retrieved from stack.
     */
    public static @NonNull Pair<LocalBluetoothLeBroadcastSourceState, Set<Integer>>
            getLocalSourceStateWithSelectedChannel(
                @NonNull LocalBluetoothProfileManager profileManager,
                @NonNull BluetoothDevice sink,
                int sourceId,
                @NonNull BluetoothLeBroadcastReceiveState state) {
        var localSourceState = getLocalSourceState(state);
        if (!Flags.audioStreamPlayPauseByModifySource()) {
            return Pair.create(localSourceState, UNKNOWN_CHANNEL);
        }
        Set<Integer> selectedChannelIndex = BluetoothUtils.getSelectedChannelIndex(
                profileManager, sink, sourceId);
        if (localSourceState == LocalBluetoothLeBroadcastSourceState.PAUSED
                && selectedChannelIndex.equals(UNKNOWN_CHANNEL)) {
            // No channel selected meaning the user decided to de-sync to the source, we return
            // `RECEIVER_PAUSED`. In contrast, having any channel selected meaning the source
            // paused itself, we return `PAUSED`.
            return Pair.create(LocalBluetoothLeBroadcastSourceState.PAUSED_BY_RECEIVER,
                    UNKNOWN_CHANNEL);
        }
        return Pair.create(localSourceState, selectedChannelIndex);
    }
}
+317 −0

File changed.

Preview size limit exceeded, changes collapsed.