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

Commit 6c060a1a authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk Committed by Rongxuan Liu
Browse files

le_audio: Introduce allowed context mask

This CL introduces handling of allowed context mask to filter stream
request in native.

Tag: #Bug
Bug: 331682466
Bug: 336468573
Test: atest LeAudioServiceTest BassClientServiceTest
Change-Id: If39bff744a4eb106ac3e1b18e844c10185791a6e
parent 1f1a6668
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -734,6 +734,23 @@ static void sendAudioProfilePreferencesNative(
      groupId, isOutputPreferenceLeAudio, isDuplexPreferenceLeAudio);
}

static void setGroupAllowedContextMaskNative(JNIEnv* /* env */,
                                             jobject /* object */, jint groupId,
                                             jint sinkContextTypes,
                                             jint sourceContextTypes) {
  std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
  if (!sLeAudioClientInterface) {
    log::error("Failed to get the Bluetooth LeAudio Interface");
    return;
  }

  log::info("group_id: {}, sink context types: {}, source context types: {}",
            groupId, sinkContextTypes, sourceContextTypes);

  sLeAudioClientInterface->SetGroupAllowedContextMask(groupId, sinkContextTypes,
                                                      sourceContextTypes);
}

/* Le Audio Broadcaster */
static jmethodID method_onBroadcastCreated;
static jmethodID method_onBroadcastDestroyed;
@@ -1592,6 +1609,8 @@ int register_com_android_bluetooth_le_audio(JNIEnv* env) {
       (void*)setUnicastMonitorModeNative},
      {"sendAudioProfilePreferencesNative", "(IZZ)V",
       (void*)sendAudioProfilePreferencesNative},
      {"setGroupAllowedContextMaskNative", "(III)V",
       (void*)setGroupAllowedContextMaskNative},
  };

  const int result = REGISTER_NATIVE_METHODS(
+53 −4
Original line number Diff line number Diff line
@@ -17,14 +17,17 @@
package com.android.bluetooth.bass_client;

import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastAudioHandoverPolicies;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastAssistantPeripheralEntrustment;
import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
@@ -139,6 +142,7 @@ public class BassClientService extends ProfileService {
    private ScanCallback mSearchScanCallback;
    private Callbacks mCallbacks;
    private boolean mIsAssistantActive = false;
    private boolean mIsAllowedContextOfActiveGroupModified = false;
    Optional<Integer> mUnicastSourceStreamStatus = Optional.empty();

    private static final int LOG_NB_EVENTS = 100;
@@ -428,6 +432,16 @@ public class BassClientService extends ProfileService {
            if (leAudioService != null) {
                leAudioService.activeBroadcastAssistantNotification(false);
            }
            mIsAssistantActive = false;
        }

        if (mIsAllowedContextOfActiveGroupModified) {
            LeAudioService leAudioService = mServiceFactory.getLeAudioService();
            if (leAudioService != null) {
                leAudioService.setActiveGroupAllowedContextMask(
                        BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
            }
            mIsAllowedContextOfActiveGroupModified = false;
        }

        synchronized (mStateMachines) {
@@ -632,7 +646,22 @@ public class BassClientService extends ProfileService {
        }
    }

    private void localNotifyReceiveStateChanged() {
    private boolean isDevicePartOfActiveUnicastGroup(BluetoothDevice device) {
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();
        if (leAudioService == null) {
            return false;
        }

        return (leAudioService.getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID)
                && (leAudioService.getActiveDevices().contains(device));
    }

    private boolean isAnyDeviceFromActiveUnicastGroupReceivingBroadcast() {
        return getActiveBroadcastSinks().stream()
                .anyMatch(d -> isDevicePartOfActiveUnicastGroup(d));
    }

    private void localNotifyReceiveStateChanged(BluetoothDevice sink) {
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();
        if (leAudioService == null) {
            return;
@@ -645,7 +674,18 @@ public class BassClientService extends ProfileService {
            if (!mIsAssistantActive) {
                mIsAssistantActive = true;
                leAudioService.activeBroadcastAssistantNotification(true);
                return;
            }

            if (leaudioAllowedContextMask()) {
                /* Don't bother active group (external broadcaster scenario) with SOUND EFFECTS */
                if (!mIsAllowedContextOfActiveGroupModified
                        && isDevicePartOfActiveUnicastGroup(sink)) {
                    leAudioService.setActiveGroupAllowedContextMask(
                            BluetoothLeAudio.CONTEXTS_ALL
                                    & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS,
                            BluetoothLeAudio.CONTEXTS_ALL);
                    mIsAllowedContextOfActiveGroupModified = true;
                }
            }
        } else {
            /* Assistant become inactive */
@@ -653,8 +693,17 @@ public class BassClientService extends ProfileService {
                mIsAssistantActive = false;
                mUnicastSourceStreamStatus = Optional.empty();
                leAudioService.activeBroadcastAssistantNotification(false);
            }

                return;
            if (leaudioAllowedContextMask()) {
                /* Restore allowed context mask for active device */
                if (mIsAllowedContextOfActiveGroupModified) {
                    if (!isAnyDeviceFromActiveUnicastGroupReceivingBroadcast()) {
                        leAudioService.setActiveGroupAllowedContextMask(
                                BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
                    }
                    mIsAllowedContextOfActiveGroupModified = false;
                }
            }
        }
    }
@@ -2571,7 +2620,7 @@ public class BassClientService extends ProfileService {
                BluetoothLeBroadcastReceiveState state) {
            ObjParams param = new ObjParams(sink, state);

            sService.localNotifyReceiveStateChanged();
            sService.localNotifyReceiveStateChanged(sink);

            String subgroupState = " / SUB GROUPS: ";
            for (int i = 0; i < state.getNumSubgroups(); i++) {
+23 −0
Original line number Diff line number Diff line
@@ -390,6 +390,26 @@ public class LeAudioNativeInterface {
                isDuplexPreferenceLeAudio);
    }

    /**
     * Set allowed context which should be considered while Audio Framework would request streaming.
     *
     * @param groupId is the groupId corresponding to the allowed context
     * @param sinkContextTypes sink context types that would be allowed to stream
     * @param sourceContextTypes source context types that would be allowed to stream
     */
    public void setGroupAllowedContextMask(
            int groupId, int sinkContextTypes, int sourceContextTypes) {
        Log.d(
                TAG,
                "setGroupAllowedContextMask groupId="
                        + groupId
                        + ", sinkContextTypes="
                        + sinkContextTypes
                        + ", sourceContextTypes="
                        + sourceContextTypes);
        setGroupAllowedContextMaskNative(groupId, sinkContextTypes, sourceContextTypes);
    }

    // Native methods that call into the JNI interface
    private native void initNative(BluetoothLeAudioCodecConfig[] codecConfigOffloading);
    private native void cleanupNative();
@@ -410,4 +430,7 @@ public class LeAudioNativeInterface {
    /*package*/
    private native void sendAudioProfilePreferencesNative(int groupId,
            boolean isOutputPreferenceLeAudio, boolean isDuplexPreferenceLeAudio);

    private native void setGroupAllowedContextMaskNative(
            int groupId, int sinkContextTypes, int sourceContextTypes);
}
+110 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
import static com.android.bluetooth.flags.Flags.leaudioApiSynchronizedBlockFix;
import static com.android.bluetooth.flags.Flags.leaudioAllowedContextMask;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

@@ -272,6 +273,8 @@ public class LeAudioService extends ProfileService {
        Boolean mInactivatedDueToContextType;

        private Integer mActiveState;
        private Integer mAllowedSinkContexts;
        private Integer mAllowedSourceContexts;

        boolean isActive() {
            return mActiveState == ACTIVE_STATE_ACTIVE;
@@ -309,6 +312,38 @@ public class LeAudioService extends ProfileService {
                    return "INVALID";
            }
        }

        void updateAllowedContexts(Integer allowedSinkContexts, Integer allowedSourceContexts) {
            Log.d(
                    TAG,
                    "LeAudioGroupDescriptor.mAllowedSinkContexts: "
                            + mAllowedSinkContexts
                            + " -> "
                            + allowedSinkContexts
                            + ", LeAudioGroupDescriptor.mAllowedSourceContexts: "
                            + mAllowedSourceContexts
                            + " -> "
                            + allowedSourceContexts);

            mAllowedSinkContexts = allowedSinkContexts;
            mAllowedSourceContexts = allowedSourceContexts;
        }

        Integer getAllowedSinkContexts() {
            return mAllowedSinkContexts;
        }

        Integer getAllowedSourceContexts() {
            return mAllowedSourceContexts;
        }

        boolean areAllowedContextsModified() {
            if ((mAllowedSinkContexts != BluetoothLeAudio.CONTEXTS_ALL)
                    || (mAllowedSourceContexts != BluetoothLeAudio.CONTEXTS_ALL)) {
                return true;
            }
            return false;
        }
    }

    private static class LeAudioDeviceDescriptor {
@@ -1442,6 +1477,22 @@ public class LeAudioService extends ProfileService {
        return true;
    }

    private Integer getFirstGroupIdInGettingActiveState() {
        mGroupReadLock.lock();
        try {
            for (Map.Entry<Integer, LeAudioGroupDescriptor> entry :
                    mGroupDescriptorsView.entrySet()) {
                LeAudioGroupDescriptor descriptor = entry.getValue();
                if (descriptor.isGettingActive()) {
                    return entry.getKey();
                }
            }
        } finally {
            mGroupReadLock.unlock();
        }
        return LE_AUDIO_GROUP_ID_INVALID;
    }

    private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) {
        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
            return null;
@@ -2530,6 +2581,30 @@ public class LeAudioService extends ProfileService {
        }
    }

    private void setGroupAllowedContextMask(
            int groupId, int sinkContextTypes, int sourceContextTypes) {
        if (!mLeAudioNativeIsInitialized) {
            Log.e(TAG, "Le Audio not initialized properly.");
            return;
        }

        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
            Log.i(TAG, "setActiveGroupAllowedContextMask: no active group");
            return;
        }

        LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId);
        if (groupDescriptor == null) {
            Log.e(TAG, "Group " + groupId + " does not exist");
            return;
        }

        groupDescriptor.updateAllowedContexts(sinkContextTypes, sourceContextTypes);

        mLeAudioNativeInterface.setGroupAllowedContextMask(
                groupId, sinkContextTypes, sourceContextTypes);
    }

    @VisibleForTesting
    void handleGroupIdleDuringCall() {
        if (mHfpHandoverDevice == null) {
@@ -3055,9 +3130,33 @@ public class LeAudioService extends ProfileService {
                        descriptor.setActiveState(ACTIVE_STATE_INACTIVE);

                        /* In case if group is inactivated due to switch to other */
                        if (!areAllGroupsInNotGettingActiveState()) {
                        Integer gettingActiveGroupId = getFirstGroupIdInGettingActiveState();
                        if (gettingActiveGroupId != LE_AUDIO_GROUP_ID_INVALID) {
                            if (leaudioAllowedContextMask()) {
                                /* Context were modified, apply mask to activating group */
                                if (descriptor.areAllowedContextsModified()) {
                                    setGroupAllowedContextMask(
                                            gettingActiveGroupId,
                                            descriptor.getAllowedSinkContexts(),
                                            descriptor.getAllowedSourceContexts());
                                    setGroupAllowedContextMask(
                                            groupId,
                                            BluetoothLeAudio.CONTEXTS_ALL,
                                            BluetoothLeAudio.CONTEXTS_ALL);
                                }
                            }
                            break;
                        }

                        if (leaudioAllowedContextMask()) {
                            /* Clear allowed context mask if there is no switch of group */
                            if (descriptor.areAllowedContextsModified()) {
                                setGroupAllowedContextMask(
                                        groupId,
                                        BluetoothLeAudio.CONTEXTS_ALL,
                                        BluetoothLeAudio.CONTEXTS_ALL);
                            }
                        }
                    } else {
                        handleGroupTransitToInactive(groupId);
                    }
@@ -3749,6 +3848,16 @@ public class LeAudioService extends ProfileService {
                isDuplexPreferenceLeAudio);
    }

    /**
     * Set allowed context which should be considered while Audio Framework would request streaming.
     *
     * @param sinkContextTypes sink context types that would be allowed to stream
     * @param sourceContextTypes source context types that would be allowed to stream
     */
    public void setActiveGroupAllowedContextMask(int sinkContextTypes, int sourceContextTypes) {
        setGroupAllowedContextMask(getActiveGroupId(), sinkContextTypes, sourceContextTypes);
    }

    /**
     * Set Inactive by HFP during handover This is a work around to handle controllers that cannot
     * have SCO and CIS at the same time. So remove active device to tear down CIS, and re-connect
+65 −0
Original line number Diff line number Diff line
@@ -2837,4 +2837,69 @@ public class LeAudioServiceTest {
                        eq(null),
                        any(BluetoothProfileConnectionInfo.class));
    }

    /** Test setting allowed contexts for active group */
    @Test
    public void testSetAllowedContextsForActiveGroup() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_GETTING_ACTIVE_STATE_SUPPORT);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOWED_CONTEXT_MASK);
        int groupId = 1;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int snkAudioLocation = 3;
        int srcAudioLocation = 4;
        int availableContexts = 5 + BluetoothLeAudio.CONTEXT_TYPE_RINGTONE;

        // Not connected device
        assertThat(mService.setActiveDevice(mSingleDevice)).isFalse();

        // Connected device
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);

        // Add location support
        LeAudioStackEvent audioConfChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED);
        audioConfChangedEvent.device = mSingleDevice;
        audioConfChangedEvent.valueInt1 = direction;
        audioConfChangedEvent.valueInt2 = groupId;
        audioConfChangedEvent.valueInt3 = snkAudioLocation;
        audioConfChangedEvent.valueInt4 = srcAudioLocation;
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);

        assertThat(mService.setActiveDevice(mSingleDevice)).isTrue();
        verify(mNativeInterface).groupSetActive(groupId);

        // Set group and device as active
        LeAudioStackEvent groupStatusChangedEvent =
                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED);
        groupStatusChangedEvent.valueInt1 = groupId;
        groupStatusChangedEvent.valueInt2 = LeAudioStackEvent.GROUP_STATUS_ACTIVE;
        mService.messageFromNative(groupStatusChangedEvent);

        // Trigger update of allowed context for active group
        int sinkContextTypes =
                BluetoothLeAudio.CONTEXTS_ALL & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS;
        int sourceContextTypes =
                BluetoothLeAudio.CONTEXTS_ALL
                        & ~(BluetoothLeAudio.CONTEXT_TYPE_NOTIFICATIONS
                                | BluetoothLeAudio.CONTEXT_TYPE_GAME);

        mService.setActiveGroupAllowedContextMask(sinkContextTypes, sourceContextTypes);
        verify(mNativeInterface)
                .setGroupAllowedContextMask(groupId, sinkContextTypes, sourceContextTypes);

        // no active device, allowed context should be reset
        assertThat(mService.removeActiveDevice(false)).isTrue();
        verify(mNativeInterface).groupSetActive(BluetoothLeAudio.GROUP_ID_INVALID);

        // Set group and device as inactive active
        groupStatusChangedEvent.valueInt2 = LeAudioStackEvent.GROUP_STATUS_INACTIVE;
        mService.messageFromNative(groupStatusChangedEvent);

        verify(mNativeInterface)
                .setGroupAllowedContextMask(
                        groupId, BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
    }
}
Loading