Loading android/app/aidl/android/bluetooth/IBluetoothLeAudio.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -119,4 +119,6 @@ oneway interface IBluetoothLeAudio { void getMaximumStreamsPerBroadcast(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT})") void getMaximumSubgroupsPerBroadcast(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void isBroadcastActive(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); } android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +25 −0 Original line number Diff line number Diff line Loading @@ -1077,6 +1077,15 @@ public class LeAudioService extends ProfileService { .collect(Collectors.toList()); } /** * Check if broadcast is active * * @return true if there is active broadcast, false otherwise */ public boolean isBroadcastActive() { return !mBroadcastDescriptors.isEmpty(); } /** * Get the maximum number of supported simultaneous broadcasts. * @return number of supported simultaneous broadcasts Loading Loading @@ -4464,6 +4473,22 @@ public class LeAudioService extends ProfileService { enforceBluetoothPrivilegedPermission(service); service.setCodecConfigPreference(groupId, inputCodecConfig, outputCodecConfig); } @Override public void isBroadcastActive( AttributionSource source, SynchronousResultReceiver receiver) { try { boolean result = false; LeAudioService service = getService(source); if (service != null) { enforceBluetoothPrivilegedPermission(service); result = service.isBroadcastActive(); } receiver.send(result); } catch (RuntimeException e) { receiver.propagateException(e); } } } @Override Loading android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java +21 −4 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import com.android.bluetooth.BluetoothEventLogger; import com.android.bluetooth.Utils; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.hearingaid.HearingAidService; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -1222,18 +1223,20 @@ public class MediaControlGattService implements MediaControlGattServiceInterface + " request up"); // TODO: Activate/deactivate devices with ActiveDeviceManager if (req.getOpcode() == Request.Opcodes.PLAY) { if (mLeAudioService == null) { mLeAudioService = LeAudioService.getLeAudioService(); } if (!isBroadcastActive() && req.getOpcode() == Request.Opcodes.PLAY) { if (mAdapterService.getActiveDevices(BluetoothProfile.A2DP).size() > 0) { A2dpService.getA2dpService().removeActiveDevice(false); } if (mAdapterService.getActiveDevices(BluetoothProfile.HEARING_AID).size() > 0) { HearingAidService.getHearingAidService().removeActiveDevice(false); } if (mLeAudioService == null) { mLeAudioService = LeAudioService.getLeAudioService(); } if (mLeAudioService != null) { mLeAudioService.setActiveDevice(device); } } mCallbacks.onMediaControlRequest(req); return BluetoothGatt.GATT_SUCCESS; Loading Loading @@ -2053,6 +2056,20 @@ public class MediaControlGattService implements MediaControlGattServiceInterface return (mFeatures & featureBit) != 0; } /** * Checks if le audio broadcasting is ON * * @return {@code true} if is broadcasting audio, {@code false} otherwise */ private boolean isBroadcastActive() { if (!Flags.leaudioBroadcastFeatureSupport()) { // disable this if feature flag is false return false; } return mLeAudioService != null && mLeAudioService.isBroadcastActive(); } @VisibleForTesting boolean isOpcodeSupported(int opcode) { if (opcode < Request.Opcodes.PLAY || opcode > Request.Opcodes.GOTO_GROUP) { Loading android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +46 −0 Original line number Diff line number Diff line Loading @@ -510,6 +510,52 @@ public class LeAudioBroadcastServiceTest { Assert.assertEquals(meta_list.get(0), state_event.broadcastMetadata); } @Test public void testIsBroadcastActive() { int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); meta_builder.setLanguage("ENG"); meta_builder.setProgramInfo("Public broadcast info"); BluetoothLeAudioContentMetadata meta = meta_builder.build(); mService.createBroadcast(buildBroadcastSettingsFromMetadata(meta, code, 1)); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); create_event.valueInt1 = broadcastId; create_event.valueBool1 = true; mService.messageFromNative(create_event); // Inject metadata stack event and verify if getter API works as expected LeAudioStackEvent state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_METADATA_CHANGED); state_event.valueInt1 = broadcastId; state_event.broadcastMetadata = createBroadcastMetadata(); mService.messageFromNative(state_event); // Verify if broadcast is active Assert.assertTrue(mService.isBroadcastActive()); mService.stopBroadcast(broadcastId); verify(mLeAudioBroadcasterNativeInterface, times(1)).stopBroadcast(eq(broadcastId)); state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); state_event.valueInt1 = broadcastId; state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STOPPED; mService.messageFromNative(state_event); verify(mLeAudioBroadcasterNativeInterface, times(1)).destroyBroadcast(eq(broadcastId)); state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED); state_event.valueInt1 = broadcastId; mService.messageFromNative(state_event); // Verify if broadcast is not active Assert.assertFalse(mService.isBroadcastActive()); } private void verifyConnectionStateIntent( int timeoutMs, BluetoothDevice device, int newState, int prevState) { Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue); Loading android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java +11 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,10 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; import android.os.Looper; import android.platform.test.annotations.RequiresFlagsEnabled; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; Loading @@ -34,6 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.le_audio.LeAudioService; import org.junit.After; Loading Loading @@ -939,13 +942,20 @@ public class MediaControlGattServiceTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_LEAUDIO_BROADCAST_FEATURE_SUPPORT) public void testMediaControlPointeRequest_OpcodePlayCallLeAudioServiceSetActiveDevice() { BluetoothGattService service = initAllFeaturesGattService(); prepareConnectedDevice(); mMcpService.updateSupportedOpcodesChar(Request.SupportedOpcodes.PLAY, true); verifyMediaControlPointRequest(service, Request.Opcodes.PLAY, null, BluetoothGatt.GATT_SUCCESS, 1); if (!Flags.leaudioBroadcastFeatureSupport()) { verify(mMockLeAudioService).setActiveDevice(any(BluetoothDevice.class)); } else { final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class); when(mMockLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList); verify(mMockMcsCallbacks, times(1)).onMediaControlRequest(any(Request.class)); } } @Test Loading Loading
android/app/aidl/android/bluetooth/IBluetoothLeAudio.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -119,4 +119,6 @@ oneway interface IBluetoothLeAudio { void getMaximumStreamsPerBroadcast(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT})") void getMaximumSubgroupsPerBroadcast(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void isBroadcastActive(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); }
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +25 −0 Original line number Diff line number Diff line Loading @@ -1077,6 +1077,15 @@ public class LeAudioService extends ProfileService { .collect(Collectors.toList()); } /** * Check if broadcast is active * * @return true if there is active broadcast, false otherwise */ public boolean isBroadcastActive() { return !mBroadcastDescriptors.isEmpty(); } /** * Get the maximum number of supported simultaneous broadcasts. * @return number of supported simultaneous broadcasts Loading Loading @@ -4464,6 +4473,22 @@ public class LeAudioService extends ProfileService { enforceBluetoothPrivilegedPermission(service); service.setCodecConfigPreference(groupId, inputCodecConfig, outputCodecConfig); } @Override public void isBroadcastActive( AttributionSource source, SynchronousResultReceiver receiver) { try { boolean result = false; LeAudioService service = getService(source); if (service != null) { enforceBluetoothPrivilegedPermission(service); result = service.isBroadcastActive(); } receiver.send(result); } catch (RuntimeException e) { receiver.propagateException(e); } } } @Override Loading
android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java +21 −4 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import com.android.bluetooth.BluetoothEventLogger; import com.android.bluetooth.Utils; import com.android.bluetooth.a2dp.A2dpService; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.hearingaid.HearingAidService; import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -1222,18 +1223,20 @@ public class MediaControlGattService implements MediaControlGattServiceInterface + " request up"); // TODO: Activate/deactivate devices with ActiveDeviceManager if (req.getOpcode() == Request.Opcodes.PLAY) { if (mLeAudioService == null) { mLeAudioService = LeAudioService.getLeAudioService(); } if (!isBroadcastActive() && req.getOpcode() == Request.Opcodes.PLAY) { if (mAdapterService.getActiveDevices(BluetoothProfile.A2DP).size() > 0) { A2dpService.getA2dpService().removeActiveDevice(false); } if (mAdapterService.getActiveDevices(BluetoothProfile.HEARING_AID).size() > 0) { HearingAidService.getHearingAidService().removeActiveDevice(false); } if (mLeAudioService == null) { mLeAudioService = LeAudioService.getLeAudioService(); } if (mLeAudioService != null) { mLeAudioService.setActiveDevice(device); } } mCallbacks.onMediaControlRequest(req); return BluetoothGatt.GATT_SUCCESS; Loading Loading @@ -2053,6 +2056,20 @@ public class MediaControlGattService implements MediaControlGattServiceInterface return (mFeatures & featureBit) != 0; } /** * Checks if le audio broadcasting is ON * * @return {@code true} if is broadcasting audio, {@code false} otherwise */ private boolean isBroadcastActive() { if (!Flags.leaudioBroadcastFeatureSupport()) { // disable this if feature flag is false return false; } return mLeAudioService != null && mLeAudioService.isBroadcastActive(); } @VisibleForTesting boolean isOpcodeSupported(int opcode) { if (opcode < Request.Opcodes.PLAY || opcode > Request.Opcodes.GOTO_GROUP) { Loading
android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java +46 −0 Original line number Diff line number Diff line Loading @@ -510,6 +510,52 @@ public class LeAudioBroadcastServiceTest { Assert.assertEquals(meta_list.get(0), state_event.broadcastMetadata); } @Test public void testIsBroadcastActive() { int broadcastId = 243; byte[] code = {0x00, 0x01, 0x00, 0x02}; BluetoothLeAudioContentMetadata.Builder meta_builder = new BluetoothLeAudioContentMetadata.Builder(); meta_builder.setLanguage("ENG"); meta_builder.setProgramInfo("Public broadcast info"); BluetoothLeAudioContentMetadata meta = meta_builder.build(); mService.createBroadcast(buildBroadcastSettingsFromMetadata(meta, code, 1)); LeAudioStackEvent create_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); create_event.valueInt1 = broadcastId; create_event.valueBool1 = true; mService.messageFromNative(create_event); // Inject metadata stack event and verify if getter API works as expected LeAudioStackEvent state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_METADATA_CHANGED); state_event.valueInt1 = broadcastId; state_event.broadcastMetadata = createBroadcastMetadata(); mService.messageFromNative(state_event); // Verify if broadcast is active Assert.assertTrue(mService.isBroadcastActive()); mService.stopBroadcast(broadcastId); verify(mLeAudioBroadcasterNativeInterface, times(1)).stopBroadcast(eq(broadcastId)); state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); state_event.valueInt1 = broadcastId; state_event.valueInt2 = LeAudioStackEvent.BROADCAST_STATE_STOPPED; mService.messageFromNative(state_event); verify(mLeAudioBroadcasterNativeInterface, times(1)).destroyBroadcast(eq(broadcastId)); state_event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED); state_event.valueInt1 = broadcastId; mService.messageFromNative(state_event); // Verify if broadcast is not active Assert.assertFalse(mService.isBroadcastActive()); } private void verifyConnectionStateIntent( int timeoutMs, BluetoothDevice device, int newState, int prevState) { Intent intent = TestUtils.waitForIntent(timeoutMs, mIntentQueue); Loading
android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java +11 −1 Original line number Diff line number Diff line Loading @@ -25,8 +25,10 @@ import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.content.Context; import android.os.Looper; import android.platform.test.annotations.RequiresFlagsEnabled; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; Loading @@ -34,6 +36,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.Flags; import com.android.bluetooth.le_audio.LeAudioService; import org.junit.After; Loading Loading @@ -939,13 +942,20 @@ public class MediaControlGattServiceTest { } @Test @RequiresFlagsEnabled(Flags.FLAG_LEAUDIO_BROADCAST_FEATURE_SUPPORT) public void testMediaControlPointeRequest_OpcodePlayCallLeAudioServiceSetActiveDevice() { BluetoothGattService service = initAllFeaturesGattService(); prepareConnectedDevice(); mMcpService.updateSupportedOpcodesChar(Request.SupportedOpcodes.PLAY, true); verifyMediaControlPointRequest(service, Request.Opcodes.PLAY, null, BluetoothGatt.GATT_SUCCESS, 1); if (!Flags.leaudioBroadcastFeatureSupport()) { verify(mMockLeAudioService).setActiveDevice(any(BluetoothDevice.class)); } else { final List<BluetoothLeBroadcastMetadata> metadataList = mock(List.class); when(mMockLeAudioService.getAllBroadcastMetadata()).thenReturn(metadataList); verify(mMockMcsCallbacks, times(1)).onMediaControlRequest(any(Request.class)); } } @Test Loading