Loading android/app/jni/com_android_bluetooth_le_audio.cpp +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +53 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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 */ Loading @@ -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; } } } } Loading Loading @@ -2603,7 +2652,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++) { Loading android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +23 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); } android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +110 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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 Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +65 −0 Original line number Diff line number Diff line Loading @@ -2841,4 +2841,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
android/app/jni/com_android_bluetooth_le_audio.cpp +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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( Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +53 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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 */ Loading @@ -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; } } } } Loading Loading @@ -2603,7 +2652,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++) { Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java +23 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); }
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +110 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 { Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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 Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java +65 −0 Original line number Diff line number Diff line Loading @@ -2841,4 +2841,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); } }