summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioService.java520
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java85
-rw-r--r--framework/java/android/bluetooth/BluetoothAdapter.java7
-rw-r--r--framework/java/android/bluetooth/BluetoothLeBroadcast.java354
-rw-r--r--system/binder/Android.bp1
-rw-r--r--system/binder/android/bluetooth/BluetoothLeAudioContentMetadata.aidl19
-rw-r--r--system/binder/android/bluetooth/IBluetoothLeAudio.aidl50
-rw-r--r--system/binder/android/bluetooth/IBluetoothLeBroadcastCallback.aidl44
-rw-r--r--system/bta/include/bta_le_audio_broadcaster_api.h10
-rw-r--r--system/bta/le_audio/broadcaster/broadcaster.cc11
-rw-r--r--system/bta/le_audio/broadcaster/broadcaster_test.cc4
-rw-r--r--system/bta/le_audio/broadcaster/mock_state_machine.h4
-rw-r--r--system/bta/le_audio/broadcaster/state_machine.cc7
-rw-r--r--system/bta/le_audio/broadcaster/state_machine.h6
-rw-r--r--system/bta/le_audio/broadcaster/state_machine_test.cc18
-rw-r--r--system/include/hardware/bt_le_audio.h5
16 files changed, 936 insertions, 209 deletions
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 6ac0a88ea8..121dc49320 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -20,13 +20,19 @@ package com.android.bluetooth.le_audio;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
+import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
+
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothLeAudio;
+import android.bluetooth.IBluetoothLeBroadcastCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,6 +44,8 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ParcelUuid;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.Utils;
@@ -50,12 +58,15 @@ import com.android.bluetooth.vc.VolumeControlService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
+import java.math.BigInteger;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -88,6 +99,12 @@ public class LeAudioService extends ProfileService {
*/
private static final int ACTIVE_CONTEXTS_NONE = 0;
+ /*
+ * Brodcast profile used by the lower layers
+ */
+ private static final int BROADCAST_PROFILE_SONIFICATION = 0;
+ private static final int BROADCAST_PROFILE_MEDIA = 1;
+
private AdapterService mAdapterService;
private DatabaseManager mDatabaseManager;
private HandlerThread mStateMachinesThread;
@@ -101,6 +118,9 @@ public class LeAudioService extends ProfileService {
@VisibleForTesting
AudioManager mAudioManager;
+ @VisibleForTesting
+ RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks;
+
private class LeAudioGroupDescriptor {
LeAudioGroupDescriptor() {
mIsConnected = false;
@@ -139,6 +159,10 @@ public class LeAudioService extends ProfileService {
private Handler mHandler = new Handler(Looper.getMainLooper());
private final Map<Integer, Integer> mBroadcastStateMap = new HashMap<>();
+ final Map<Integer, Integer> mBroadcastIdMap = new HashMap<>();
+ private final Map<Integer, Boolean> mBroadcastsPlaybackMap = new HashMap<>();
+ private final List<BluetoothLeBroadcastMetadata> mBroadcastMetadataList = new ArrayList<>();
+
@Override
protected IProfileServiceBinder initBinder() {
return new BluetoothLeAudioBinder(this);
@@ -174,6 +198,10 @@ public class LeAudioService extends ProfileService {
mDeviceGroupIdMap.clear();
mBroadcastStateMap.clear();
+ mBroadcastIdMap.clear();
+ mBroadcastMetadataList.clear();
+ mBroadcastsPlaybackMap.clear();
+
mGroupDescriptors.clear();
// Setup broadcast receivers
@@ -188,6 +216,7 @@ public class LeAudioService extends ProfileService {
// Initialize Broadcast native interface
if (mAdapterService.isLeAudioBroadcastSourceSupported()) {
+ mBroadcastCallbacks = new RemoteCallbackList<IBluetoothLeBroadcastCallback>();
mLeAudioBroadcasterNativeInterface = Objects.requireNonNull(
LeAudioBroadcasterNativeInterface.getInstance(),
"LeAudioBroadcasterNativeInterface cannot be null when LeAudioService starts");
@@ -256,7 +285,15 @@ public class LeAudioService extends ProfileService {
mDeviceGroupIdMap.clear();
mGroupDescriptors.clear();
+ if (mBroadcastCallbacks != null) {
+ mBroadcastCallbacks.kill();
+ }
+
mBroadcastStateMap.clear();
+ mBroadcastIdMap.clear();
+ mBroadcastsPlaybackMap.clear();
+ mBroadcastMetadataList.clear();
+
if (mLeAudioBroadcasterNativeInterface != null) {
mLeAudioBroadcasterNativeInterface.cleanup();
mLeAudioBroadcasterNativeInterface = null;
@@ -549,22 +586,13 @@ public class LeAudioService extends ProfileService {
* @param audioProfile broadcast audio profile
* @param broadcastCode optional code if broadcast should be encrypted
*/
- public void createBroadcast(byte[] metadata, int audioProfile, byte[] broadcastCode) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.createBroadcast(metadata, audioProfile,
- broadcastCode);
- }
- }
-
- /**
- * Updates LeAudio Broadcast instance metadata.
- * @param instanceId broadcast instance identifier
- * @param metadata metadata buffer with TLVs
- */
- public void updateMetadata(int instanceId, byte[] metadata) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.updateMetadata(instanceId, metadata);
+ public void createBroadcast(BluetoothLeAudioContentMetadata metadata, byte[] broadcastCode) {
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ mLeAudioBroadcasterNativeInterface.createBroadcast(metadata.getRawMetadata(),
+ BROADCAST_PROFILE_MEDIA, broadcastCode);
}
/**
@@ -572,29 +600,39 @@ public class LeAudioService extends ProfileService {
* @param instanceId broadcast instance identifier
*/
public void startBroadcast(int instanceId) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.startBroadcast(instanceId);
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ if (DBG) Log.d(TAG, "startBroadcast");
+ mLeAudioBroadcasterNativeInterface.startBroadcast(instanceId);
}
/**
- * Stop LeAudio Broadcast instance.
+ * Updates LeAudio Broadcast instance metadata.
* @param instanceId broadcast instance identifier
+ * @param metadata metadata for the default Broadcast subgroup
*/
- public void stopBroadcast(int instanceId) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.stopBroadcast(instanceId);
+ public void updateBroadcast(int instanceId, BluetoothLeAudioContentMetadata metadata) {
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ if (DBG) Log.d(TAG, "updateBroadcast");
+ mLeAudioBroadcasterNativeInterface.updateMetadata(instanceId, metadata.getRawMetadata());
}
/**
- * Pause LeAudio Broadcast instance.
+ * Stop LeAudio Broadcast instance.
* @param instanceId broadcast instance identifier
*/
- public void pauseBroadcast(int instanceId) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.pauseBroadcast(instanceId);
+ public void stopBroadcast(Integer instanceId) {
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ if (DBG) Log.d(TAG, "stopBroadcast");
+ mLeAudioBroadcasterNativeInterface.stopBroadcast(instanceId);
}
/**
@@ -602,9 +640,12 @@ public class LeAudioService extends ProfileService {
* @param instanceId broadcast instance identifier
*/
public void destroyBroadcast(int instanceId) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.destroyBroadcast(instanceId);
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ if (DBG) Log.d(TAG, "destroyBroadcast");
+ mLeAudioBroadcasterNativeInterface.destroyBroadcast(instanceId);
}
/**
@@ -612,18 +653,37 @@ public class LeAudioService extends ProfileService {
* @param instanceId broadcast instance identifier
*/
public void getBroadcastId(int instanceId) {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.getBroadcastId(instanceId);
+ if (mLeAudioBroadcasterNativeInterface == null) {
+ Log.w(TAG, "Native interface not available.");
+ return;
}
+ mLeAudioBroadcasterNativeInterface.getBroadcastId(instanceId);
}
/**
- * Get all LeAudio Broadcast instance states.
+ * Checks if Broadcast instance is playing.
+ * @param instanceId broadcast instance identifier
+ * @return true if if broadcast is playing, false otherwise
*/
- public void getAllBroadcastStates() {
- if (mLeAudioBroadcasterNativeInterface != null) {
- mLeAudioBroadcasterNativeInterface.getAllBroadcastStates();
- }
+ public boolean isPlaying(int instanceId) {
+ return mBroadcastsPlaybackMap.getOrDefault(instanceId, false);
+ }
+
+ /**
+ * Get all broadcast metadata.
+ * @return list of all know Broadcast metadata
+ */
+ public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+ return mBroadcastMetadataList;
+ }
+
+ /**
+ * Get the maximum number of supported simultaneous broadcasts.
+ * @return number of supported simultaneous broadcasts
+ */
+ public int getMaximumNumberOfBroadcast() {
+ /* TODO: This is currently fixed to 1 */
+ return 1;
}
private BluetoothDevice getFirstDeviceFromGroup(Integer groupId) {
@@ -1048,40 +1108,102 @@ public class LeAudioService extends ProfileService {
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED) {
- // TODO: Implement
+ int instanceId = stackEvent.valueInt1;
+ boolean success = stackEvent.valueBool1;
+ if (success) {
+ Log.d(TAG, "Broadcast Instance id: " + instanceId + " created.");
+ startBroadcast(instanceId);
+ getBroadcastId(instanceId);
+ } else {
+ // TODO: Improve reason reporting or extend the native stack event with reason code
+ notifyBroadcastStartFailed(instanceId, BluetoothStatusCodes.ERROR_UNKNOWN);
+ }
+
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED) {
- // TODO: Implement
+ Integer instanceId = stackEvent.valueInt1;
+
+ // TODO: Improve reason reporting or extend the native stack event with reason code
+ notifyOnBroadcastStopped(instanceId, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+ mBroadcastStateMap.remove(instanceId);
+ mBroadcastsPlaybackMap.remove(instanceId);
+ if (mBroadcastIdMap.containsKey(instanceId)) {
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ mBroadcastMetadataList.removeIf(m -> broadcastId == m.getBroadcastId());
+ mBroadcastIdMap.remove(instanceId);
+ }
+
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE) {
int instanceId = stackEvent.valueInt1;
int state = stackEvent.valueInt2;
+ mBroadcastStateMap.put(instanceId, state);
- if (state == LeAudioStackEvent.BROADCAST_STATE_STREAMING) {
- mBroadcastStateMap.put(instanceId, state);
- if (mBroadcastStateMap.size() == 1) {
- if (!Objects.equals(device, mActiveAudioOutDevice)) {
+ if (state == LeAudioStackEvent.BROADCAST_STATE_STOPPED) {
+ if (DBG) Log.d(TAG, "Broadcast Instance id: " + instanceId + " stopped.");
+ destroyBroadcast(instanceId);
+
+ } else if (state == LeAudioStackEvent.BROADCAST_STATE_CONFIGURING) {
+ if (DBG) Log.d(TAG, "Broadcast Instance id: " + instanceId + " configuring.");
+
+ } else if (state == LeAudioStackEvent.BROADCAST_STATE_PAUSED) {
+ if (DBG) Log.d(TAG, "Broadcast Instance id: " + instanceId + " paused.");
+
+ if (!mBroadcastsPlaybackMap.containsKey(instanceId)) {
+ // Initial playback state after the creation
+ notifyBroadcastStarted(instanceId,
+ BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+ }
+
+ // Playback paused
+ mBroadcastsPlaybackMap.put(instanceId, false);
+ notifyPlaybackStopped(instanceId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);
+
+ // Notify audio manager
+ if (Collections.frequency(mBroadcastsPlaybackMap.values(), true) == 0) {
+ if (Objects.equals(device, mActiveAudioOutDevice)) {
BluetoothDevice previousDevice = mActiveAudioOutDevice;
- mActiveAudioOutDevice = device;
+ mActiveAudioOutDevice = null;
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
previousDevice,
- BluetoothProfileConnectionInfo.createLeAudioInfo(false, true));
+ // TODO: implement createLeAudioBroadcastInfo()
+ BluetoothProfileConnectionInfo.createLeAudioInfo(true, true));
}
}
- } else {
- mBroadcastStateMap.remove(instanceId);
- if (mBroadcastStateMap.size() == 0) {
- if (Objects.equals(device, mActiveAudioOutDevice)) {
+
+ } else if (state == LeAudioStackEvent.BROADCAST_STATE_STOPPING) {
+ if (DBG) Log.d(TAG, "Broadcast Instance id: " + instanceId + " stopping.");
+
+ } else if (state == LeAudioStackEvent.BROADCAST_STATE_STREAMING) {
+ if (DBG) Log.d(TAG, "Broadcast Instance id: " + instanceId + " streaming.");
+
+ if (!mBroadcastsPlaybackMap.containsKey(instanceId)) {
+ notifyBroadcastStarted(instanceId,
+ BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+ }
+
+ // Stream resumed
+ mBroadcastsPlaybackMap.put(instanceId, true);
+ notifyPlaybackStarted(instanceId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);
+
+ // Notify audio manager
+ if (Collections.frequency(mBroadcastsPlaybackMap.values(), true) == 1) {
+ if (!Objects.equals(device, mActiveAudioOutDevice)) {
BluetoothDevice previousDevice = mActiveAudioOutDevice;
- mActiveAudioOutDevice = null;
+ mActiveAudioOutDevice = device;
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
previousDevice,
- BluetoothProfileConnectionInfo.createLeAudioInfo(true, true));
+ // TODO: implement createLeAudioBroadcastInfo()
+ BluetoothProfileConnectionInfo.createLeAudioInfo(false, true));
}
}
}
- // TODO: Implement
+
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_ID) {
- // TODO: Implement
+ int instanceId = stackEvent.valueInt1;
+ byte[] broadcastId = stackEvent.valueByte1;
+ mBroadcastIdMap.put(instanceId, new BigInteger(broadcastId).intValue());
}
+ // TODO: Support Broadcast metadata updates
if (intent != null) {
sendBroadcast(intent, BLUETOOTH_CONNECT);
@@ -1401,6 +1523,177 @@ public class LeAudioService extends ProfileService {
}
}
+ private void notifyBroadcastStarted(Integer instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onBroadcastStarted(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyBroadcastStartFailed(Integer instanceId, int reason) {
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onBroadcastStartFailed(reason);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyOnBroadcastStopped(Integer instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onBroadcastStopped(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyOnBroadcastStopFailed(int reason) {
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onBroadcastStopFailed(reason);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyPlaybackStarted(Integer instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onPlaybackStarted(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyPlaybackStopped(Integer instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onPlaybackStopped(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyBroadcastUpdated(int instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i).onBroadcastUpdated(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyBroadcastUpdateFailed(int instanceId, int reason) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i)
+ .onBroadcastUpdateFailed(reason, broadcastId);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
+ private void notifyBroadcastMetadataChanged(int instanceId,
+ BluetoothLeBroadcastMetadata metadata) {
+ if (!mBroadcastIdMap.containsKey(instanceId)) {
+ Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
+ return;
+ }
+
+ Integer broadcastId = mBroadcastIdMap.get(instanceId);
+ if (mBroadcastCallbacks != null) {
+ int n = mBroadcastCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mBroadcastCallbacks.getBroadcastItem(i)
+ .onBroadcastMetadataChanged(broadcastId, metadata);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mBroadcastCallbacks.finishBroadcast();
+ }
+ }
+
/**
* Binder object: must be a static class or memory leak may occur
*/
@@ -1638,84 +1931,131 @@ public class LeAudioService extends ProfileService {
}
@Override
- public void createBroadcast(byte[] metadata, int audioProfile, byte[] broadcastCode,
- AttributionSource source) {
+ public void registerLeBroadcastCallback(IBluetoothLeBroadcastCallback callback,
+ AttributionSource source, SynchronousResultReceiver receiver) {
LeAudioService service = getService(source);
- if (service == null) {
+ if ((service == null) || (service.mBroadcastCallbacks == null)) {
+ receiver.propagateException(new IllegalStateException("Service is unavailable"));
return;
}
- service.createBroadcast(metadata, audioProfile, broadcastCode);
+ enforceBluetoothPrivilegedPermission(service);
+ try {
+ service.mBroadcastCallbacks.register(callback);
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
}
@Override
- public void updateMetadata(int instanceId, byte[] metadata, AttributionSource source) {
+ public void unregisterLeBroadcastCallback(IBluetoothLeBroadcastCallback callback,
+ AttributionSource source, SynchronousResultReceiver receiver) {
LeAudioService service = getService(source);
- if (service == null) {
+ if ((service == null) || (service.mBroadcastCallbacks == null)) {
+ receiver.propagateException(new IllegalStateException("Service is unavailable"));
return;
}
- service.updateMetadata(instanceId, metadata);
+ enforceBluetoothPrivilegedPermission(service);
+ try {
+ service.mBroadcastCallbacks.unregister(callback);
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
}
@Override
- public void startBroadcast(int instanceId, AttributionSource source) {
+ public void startBroadcast(BluetoothLeAudioContentMetadata contentMetadata,
+ byte[] broadcastCode, AttributionSource source) {
LeAudioService service = getService(source);
- if (service == null) {
- return;
+ if (service != null) {
+ service.createBroadcast(contentMetadata, broadcastCode);
}
-
- service.startBroadcast(instanceId);
}
@Override
- public void stopBroadcast(int instanceId, AttributionSource source) {
+ public void stopBroadcast(int broadcastId, AttributionSource source) {
LeAudioService service = getService(source);
- if (service == null) {
- return;
+ if (service != null) {
+ Optional<Integer> instanceId = service.mBroadcastIdMap.entrySet()
+ .stream()
+ .filter(entry -> Objects.equals(entry.getValue(), broadcastId))
+ .map(Map.Entry::getKey)
+ .findFirst();
+ if (instanceId.isPresent()) {
+ service.stopBroadcast(instanceId.get());
+ }
}
-
- service.stopBroadcast(instanceId);
}
@Override
- public void pauseBroadcast(int instanceId, AttributionSource source) {
+ public void updateBroadcast(int broadcastId,
+ BluetoothLeAudioContentMetadata contentMetadata, AttributionSource source) {
LeAudioService service = getService(source);
- if (service == null) {
- return;
+ if (service != null) {
+ Optional<Integer> instanceId = service.mBroadcastIdMap.entrySet()
+ .stream()
+ .filter(entry -> Objects.equals(entry.getValue(), broadcastId))
+ .map(Map.Entry::getKey)
+ .findFirst();
+ if (instanceId.isPresent()) {
+ service.updateBroadcast(instanceId.get(), contentMetadata);
+ }
}
-
- service.pauseBroadcast(instanceId);
}
@Override
- public void destroyBroadcast(int instanceId, AttributionSource source) {
- LeAudioService service = getService(source);
- if (service == null) {
- return;
+ public void isPlaying(int broadcastId, AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ boolean defaultValue = false;
+ LeAudioService service = getService(source);
+ if (service != null) {
+ Optional<Integer> instanceId = service.mBroadcastIdMap.entrySet()
+ .stream()
+ .filter(entry -> Objects.equals(entry.getValue(), broadcastId))
+ .map(Map.Entry::getKey)
+ .findFirst();
+ if (instanceId.isPresent()) {
+ defaultValue = service.isPlaying(instanceId.get());
+ }
+ }
+ receiver.send(defaultValue);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
}
-
- service.destroyBroadcast(instanceId);
}
@Override
- public void getBroadcastId(int instanceId, AttributionSource source) {
- LeAudioService service = getService(source);
- if (service == null) {
- return;
+ public void getAllBroadcastMetadata(AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ List<BluetoothLeBroadcastMetadata> defaultValue = new ArrayList<>();
+ LeAudioService service = getService(source);
+ if (service != null) {
+ defaultValue = service.getAllBroadcastMetadata();
+ }
+ receiver.send(defaultValue);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
}
-
- service.getBroadcastId(instanceId);
}
@Override
- public void getAllBroadcastStates(AttributionSource source) {
- LeAudioService service = getService(source);
- if (service == null) {
- return;
+ public void getMaximumNumberOfBroadcast(AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ int defaultValue = 0;
+ LeAudioService service = getService(source);
+ if (service != null) {
+ defaultValue = service.getMaximumNumberOfBroadcast();
+ }
+ receiver.send(defaultValue);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
}
-
- service.getAllBroadcastStates();
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
index 70e1dee146..9dce7a6470 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
@@ -150,85 +150,56 @@ public class LeAudioBroadcastServiceTest {
@Test
public void testCreateBroadcastNative() {
int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
- verify(mNativeInterface, times(1)).createBroadcast(eq(meta),
- eq(broadcast_profile), eq(code));
- }
+ BluetoothLeAudioContentMetadata.Builder meta_builder =
+ new BluetoothLeAudioContentMetadata.Builder();
+ meta_builder.setLanguage("EN");
+ meta_builder.setProgramInfo("Public broadcast info");
+ BluetoothLeAudioContentMetadata meta = meta_builder.build();
+ mService.createBroadcast(meta, code);
- @Test
- public void testStartBroadcastNative() {
- int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
- byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
-
- int broadcast_id = 243;
- mService.startBroadcast(broadcast_id);
- verify(mNativeInterface, times(1)).startBroadcast(eq(broadcast_id));
+ verify(mNativeInterface, times(1)).createBroadcast(eq(meta.getRawMetadata()), eq(1),
+ eq(code));
}
@Test
- public void testStopBroadcastNative() {
+ public void testStartStopBroadcastNative() {
int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
- int broadcast_id = 243;
- mService.stopBroadcast(broadcast_id);
- verify(mNativeInterface, times(1)).stopBroadcast(eq(broadcast_id));
- }
+ BluetoothLeAudioContentMetadata.Builder meta_builder =
+ new BluetoothLeAudioContentMetadata.Builder();
+ meta_builder.setLanguage("EN");
+ meta_builder.setProgramInfo("Public broadcast info");
+ BluetoothLeAudioContentMetadata meta = meta_builder.build();
+ mService.createBroadcast(meta, code);
- @Test
- public void testPauseBroadcastNative() {
- int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
- byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
+ int instance_id = 243;
+ mService.startBroadcast(instance_id);
+ verify(mNativeInterface, times(1)).startBroadcast(eq(instance_id));
- int broadcast_id = 243;
- mService.pauseBroadcast(broadcast_id);
- verify(mNativeInterface, times(1)).pauseBroadcast(eq(broadcast_id));
+ mService.stopBroadcast(instance_id);
+ verify(mNativeInterface, times(1)).stopBroadcast(eq(instance_id));
}
@Test
public void testDestroyBroadcastNative() {
int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
+
+ BluetoothLeAudioContentMetadata.Builder meta_builder =
+ new BluetoothLeAudioContentMetadata.Builder();
+ meta_builder.setLanguage("ENG");
+ meta_builder.setProgramInfo("Public broadcast info");
+ BluetoothLeAudioContentMetadata meta = meta_builder.build();
+ mService.createBroadcast(meta, code);
int broadcast_id = 243;
mService.destroyBroadcast(broadcast_id);
verify(mNativeInterface, times(1)).destroyBroadcast(eq(broadcast_id));
}
-
- @Test
- public void testGetBroadcastAddressNative() {
- int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
- byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
-
- int broadcast_id = 243;
- mService.getBroadcastId(broadcast_id);
- verify(mNativeInterface, times(1)).getBroadcastId(eq(broadcast_id));
- }
-
- @Test
- public void testGetAllBroadcastStates() {
- int broadcast_profile = 0;
- byte[] meta = new byte[]{0x02, 0x01, 0x02};
- byte[] code = {0x00, 0x01, 0x00};
- mService.createBroadcast(meta, broadcast_profile, code);
-
- int broadcast_id = 243;
- mService.getAllBroadcastStates();
- verify(mNativeInterface, times(1)).getAllBroadcastStates();
- }
+ // FIXME: Add the missign API test cases
private class LeAudioIntentReceiver extends BroadcastReceiver {
@Override
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index 15f0f923c1..936a08569b 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -3480,6 +3480,9 @@ public final class BluetoothAdapter {
} else if (profile == BluetoothProfile.LE_AUDIO) {
BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this);
return true;
+ } else if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
+ BluetoothLeBroadcast leAudio = new BluetoothLeBroadcast(context, listener);
+ return true;
} else if (profile == BluetoothProfile.VOLUME_CONTROL) {
BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this);
return true;
@@ -3587,6 +3590,10 @@ public final class BluetoothAdapter {
BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy;
leAudio.close();
break;
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ BluetoothLeBroadcast leAudioBroadcast = (BluetoothLeBroadcast) proxy;
+ leAudioBroadcast.close();
+ break;
case BluetoothProfile.VOLUME_CONTROL:
BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy;
vcs.close();
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcast.java b/framework/java/android/bluetooth/BluetoothLeBroadcast.java
index 4537cc6f97..5bf6e00fea 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcast.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcast.java
@@ -16,21 +16,33 @@
package android.bluetooth;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.content.AttributionSource;
import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.CloseGuard;
import android.util.Log;
+import com.android.modules.utils.SynchronousResultReceiver;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
/**
* This class provides the public APIs to control the BAP Broadcast Source profile.
@@ -45,6 +57,148 @@ import java.util.concurrent.Executor;
public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfile {
private static final String TAG = "BluetoothLeBroadcast";
private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private CloseGuard mCloseGuard;
+
+ private final BluetoothAdapter mAdapter;
+ private final AttributionSource mAttributionSource;
+ private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector =
+ new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO_BROADCAST,
+ "BluetoothLeAudioBroadcast", IBluetoothLeAudio.class.getName()) {
+ @Override
+ public IBluetoothLeAudio getServiceInterface(IBinder service) {
+ return IBluetoothLeAudio.Stub.asInterface(service);
+ }
+ };
+
+ private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>();
+
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothLeBroadcastCallback mCallback =
+ new IBluetoothLeBroadcastCallback.Stub() {
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastStarted(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastStartFailed(reason));
+ }
+ }
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastStopped(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastStopFailed(reason));
+ }
+ }
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onPlaybackStarted(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onPlaybackStopped(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastUpdated(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastUpdateFailed(reason, broadcastId));
+ }
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(int broadcastId,
+ BluetoothLeBroadcastMetadata metadata) {
+ for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
+ mCallbackExecutorMap.entrySet()) {
+ BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onBroadcastMetadataChanged(broadcastId, metadata));
+ }
+ }
+ };
+
+ @SuppressLint("AndroidFrameworkBluetoothPermission")
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (up) {
+ // re-register the service-to-app callback
+ synchronized (mCallbackExecutorMap) {
+ if (!mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerLeBroadcastCallback(mCallback,
+ mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout())
+ .getValue(null);
+ }
+ } catch (TimeoutException e) {
+ Log.e(TAG, "onBluetoothServiceUp: Failed to register "
+ + "Le Broadcaster callback", e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+ }
+ };
/**
* Interface for receiving events related to Broadcast Source
@@ -171,7 +325,33 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
* @param listener listens for service callbacks across binder
* @hide
*/
- /*package*/ BluetoothLeBroadcast(Context context, BluetoothProfile.ServiceListener listener) {}
+ /*package*/ BluetoothLeBroadcast(Context context, BluetoothProfile.ServiceListener listener) {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAttributionSource = mAdapter.getAttributionSource();
+ mProfileConnector.connect(context, listener);
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ mCloseGuard = new CloseGuard();
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * @hide
+ */
+ protected void finalize() {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
/**
* Not supported since LE Audio Broadcasts do not establish a connection.
@@ -246,8 +426,36 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
- log("registerCallback");
- throw new UnsupportedOperationException("Not Implemented");
+ if (!isEnabled()) {
+ throw new IllegalStateException("service not enabled");
+ }
+
+ if (DBG) log("registerCallback");
+
+ synchronized (mCallbackExecutorMap) {
+ // If the callback map is empty, we register the service-to-app callback
+ if (mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv =
+ new SynchronousResultReceiver();
+ service.registerLeBroadcastCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (TimeoutException | IllegalStateException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ // Adds the passed in callback to our map of callbacks to executors
+ if (mCallbackExecutorMap.containsKey(callback)) {
+ throw new IllegalArgumentException("This callback has already been registered");
+ }
+ mCallbackExecutorMap.put(callback, executor);
+ }
}
/**
@@ -271,8 +479,30 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
- log("unregisterCallback");
- throw new UnsupportedOperationException("Not Implemented");
+
+ if (DBG) log("unregisterCallback");
+
+ synchronized (mCallbackExecutorMap) {
+ if (mCallbackExecutorMap.remove(callback) != null) {
+ throw new IllegalArgumentException("This callback has not been registered");
+ }
+ }
+
+ // If the callback map is empty, we unregister the service-to-app callback
+ if (mCallbackExecutorMap.isEmpty()) {
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null) {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.unregisterLeBroadcastCallback(mCallback, mAttributionSource, recv);
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ }
+ } catch (TimeoutException | IllegalStateException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -323,6 +553,17 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
public void startBroadcast(@NonNull BluetoothLeAudioContentMetadata contentMetadata,
@Nullable byte[] broadcastCode) {
if (DBG) log("startBroadcasting");
+ final IBluetoothLeAudio service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ service.startBroadcast(contentMetadata, broadcastCode, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -346,7 +587,18 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
})
public void updateBroadcast(int broadcastId,
@NonNull BluetoothLeAudioContentMetadata contentMetadata) {
-
+ if (DBG) log("updateBroadcast");
+ final IBluetoothLeAudio service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ service.updateBroadcast(broadcastId, contentMetadata, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -368,6 +620,17 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
})
public void stopBroadcast(int broadcastId) {
if (DBG) log("disableBroadcastMode");
+ final IBluetoothLeAudio service = getService();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ service.stopBroadcast(broadcastId, mAttributionSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -385,7 +648,23 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
})
public boolean isPlaying(int broadcastId) {
- return false;
+ final IBluetoothLeAudio service = getService();
+ final boolean defaultValue = false;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+ service.isPlaying(broadcastId, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return defaultValue;
}
/**
@@ -402,7 +681,24 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
})
public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
- return Collections.emptyList();
+ final IBluetoothLeAudio service = getService();
+ final List<BluetoothLeBroadcastMetadata> defaultValue = Collections.emptyList();
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<List<BluetoothLeBroadcastMetadata>> recv =
+ new SynchronousResultReceiver();
+ service.getAllBroadcastMetadata(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return defaultValue;
}
/**
@@ -413,7 +709,23 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
@SystemApi
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public int getMaximumNumberOfBroadcast() {
- return 1;
+ final IBluetoothLeAudio service = getService();
+ final int defaultValue = 1;
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (isEnabled()) {
+ try {
+ final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+ service.getMaximumNumberOfBroadcast(mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return defaultValue;
}
/**
@@ -421,7 +733,29 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
* @hide
*/
@Override
- public void close() throws Exception {}
+ public void close() {
+ if (VDBG) log("close()");
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+
+ mProfileConnector.disconnect();
+ }
+
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private IBluetoothLeAudio getService() {
+ return mProfileConnector.getService();
+ }
private static void log(String msg) {
Log.d(TAG, msg);
diff --git a/system/binder/Android.bp b/system/binder/Android.bp
index 805f68c8cd..ff18409fd4 100644
--- a/system/binder/Android.bp
+++ b/system/binder/Android.bp
@@ -30,6 +30,7 @@ filegroup {
"android/bluetooth/IBluetoothVolumeControl.aidl",
"android/bluetooth/IBluetoothHidHost.aidl",
"android/bluetooth/IBluetoothLeAudio.aidl",
+ "android/bluetooth/IBluetoothLeBroadcastCallback.aidl",
"android/bluetooth/IBluetoothManager.aidl",
"android/bluetooth/IBluetoothManagerCallback.aidl",
"android/bluetooth/IBluetoothMap.aidl",
diff --git a/system/binder/android/bluetooth/BluetoothLeAudioContentMetadata.aidl b/system/binder/android/bluetooth/BluetoothLeAudioContentMetadata.aidl
new file mode 100644
index 0000000000..8962810c62
--- /dev/null
+++ b/system/binder/android/bluetooth/BluetoothLeAudioContentMetadata.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+parcelable BluetoothLeAudioContentMetadata;
diff --git a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
index 337b9babd8..22a90a0758 100644
--- a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
+++ b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
@@ -18,6 +18,8 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.IBluetoothLeBroadcastCallback;
import android.content.AttributionSource;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -73,36 +75,20 @@ oneway interface IBluetoothLeAudio {
void groupRemoveNode(int group_id, in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
// Broadcaster API
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void createBroadcast(in byte[] metadata, int audio_profile, in byte[] broadcast_code, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void updateMetadata(int instance_id, in byte[] metadata, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void startBroadcast(int instance_id, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void stopBroadcast(int instance_id, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void pauseBroadcast(int instance_id, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void destroyBroadcast(int instance_id, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void getBroadcastId(int instance_id, in AttributionSource attributionSource);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void getAllBroadcastStates(in AttributionSource attributionSource);
-
- const int BROADCAST_INSTANCE_ID_UNDEFINED = 0xFF;
-
- const int BROADCAST_STATE_STOPPED = 0;
- const int BROADCAST_STATE_CONFIGURING = 1;
- const int BROADCAST_STATE_CONFIGURED = 2;
- const int BROADCAST_STATE_STOPPING = 3;
- const int BROADCAST_STATE_STREAMING = 4;
-
- const int BROADCASTER_ADDR_TYPE_PUBLIC = 0x00;
- const int BROADCASTER_ADDR_TYPE_RANDOM = 0x01;
-
- const int BROADCAST_PROFILE_SONIFICATION = 0x00;
- const int BROADCAST_PROFILE_MEDIA = 0x01;
-
- const int BIS_SYNC_ANY = 0xFFFFFFFF;
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void registerLeBroadcastCallback(in IBluetoothLeBroadcastCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void unregisterLeBroadcastCallback(in IBluetoothLeBroadcastCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void startBroadcast(in BluetoothLeAudioContentMetadata contentMetadata, in byte[] broadcastCode, in AttributionSource attributionSource);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void stopBroadcast(int broadcastId, in AttributionSource attributionSource);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void updateBroadcast(int broadcastId, in BluetoothLeAudioContentMetadata contentMetadata, in AttributionSource attributionSource);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT})")
+ void isPlaying(int broadcastId, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void getAllBroadcastMetadata(in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT})")
+ void getMaximumNumberOfBroadcast(in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
}
diff --git a/system/binder/android/bluetooth/IBluetoothLeBroadcastCallback.aidl b/system/binder/android/bluetooth/IBluetoothLeBroadcastCallback.aidl
new file mode 100644
index 0000000000..d4df7c8dd5
--- /dev/null
+++ b/system/binder/android/bluetooth/IBluetoothLeBroadcastCallback.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+
+/**
+ * Callback definitions for interacting with Le Audio Broadcaster
+ * @hide
+ */
+oneway interface IBluetoothLeBroadcastCallback {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastStarted(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastStartFailed(in int reason);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastStopped(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastStopFailed(in int reason);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onPlaybackStarted(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onPlaybackStopped(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastUpdated(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastUpdateFailed(in int reason, in int broadcastId);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)")
+ void onBroadcastMetadataChanged(in int broadcastId, in BluetoothLeBroadcastMetadata metadata);
+}
diff --git a/system/bta/include/bta_le_audio_broadcaster_api.h b/system/bta/include/bta_le_audio_broadcaster_api.h
index 428409c8fa..33022ef19b 100644
--- a/system/bta/include/bta_le_audio_broadcaster_api.h
+++ b/system/bta/include/bta_le_audio_broadcaster_api.h
@@ -26,8 +26,6 @@
/* Interface class */
class LeAudioBroadcaster {
public:
- typedef std::array<uint8_t, 16> Code;
-
enum class AudioProfile {
SONIFICATION = 0,
MEDIA = 1,
@@ -47,10 +45,10 @@ class LeAudioBroadcaster {
static bool IsLeAudioBroadcasterRunning(void);
static void DebugDump(int fd);
- virtual void CreateAudioBroadcast(std::vector<uint8_t> metadata,
- AudioProfile profile,
- std::optional<LeAudioBroadcaster::Code>
- broadcast_code = std::nullopt) = 0;
+ virtual void CreateAudioBroadcast(
+ std::vector<uint8_t> metadata, AudioProfile profile,
+ std::optional<bluetooth::le_audio::BroadcastCode> broadcast_code =
+ std::nullopt) = 0;
virtual void SuspendAudioBroadcast(uint8_t instance_id) = 0;
virtual void StartAudioBroadcast(uint8_t instance_id) = 0;
virtual void StopAudioBroadcast(uint8_t instance_id) = 0;
diff --git a/system/bta/le_audio/broadcaster/broadcaster.cc b/system/bta/le_audio/broadcaster/broadcaster.cc
index 2744fe9fc9..43460e4ecb 100644
--- a/system/bta/le_audio/broadcaster/broadcaster.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster.cc
@@ -172,9 +172,10 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
std::move(announcement));
}
- void CreateAudioBroadcast(
- std::vector<uint8_t> metadata, LeAudioBroadcaster::AudioProfile profile,
- std::optional<LeAudioBroadcaster::Code> broadcast_code) override {
+ void CreateAudioBroadcast(std::vector<uint8_t> metadata,
+ LeAudioBroadcaster::AudioProfile profile,
+ std::optional<bluetooth::le_audio::BroadcastCode>
+ broadcast_code) override {
DLOG(INFO) << __func__;
auto& codec_wrapper =
@@ -686,7 +687,7 @@ LeAudioBroadcasterImpl::LeAudioClientAudioSinkReceiverImpl
void LeAudioBroadcaster::Initialize(
bluetooth::le_audio::LeAudioBroadcasterCallbacks* callbacks,
- base::Callback<bool()> hal_2_1_verifier) {
+ base::Callback<bool()> audio_hal_verifier) {
LOG(INFO) << "Broadcaster " << __func__;
if (instance) {
LOG(ERROR) << "Already initialized";
@@ -701,7 +702,7 @@ void LeAudioBroadcaster::Initialize(
IsoManager::GetInstance()->Start();
- if (!std::move(hal_2_1_verifier).Run()) {
+ if (!std::move(audio_hal_verifier).Run()) {
LOG_ASSERT(false) << __func__ << ", HAL 2.1 not supported, Init aborted.";
return;
}
diff --git a/system/bta/le_audio/broadcaster/broadcaster_test.cc b/system/bta/le_audio/broadcaster/broadcaster_test.cc
index e0466dd041..d028451105 100644
--- a/system/bta/le_audio/broadcaster/broadcaster_test.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster_test.cc
@@ -64,7 +64,7 @@ namespace broadcaster {
namespace {
static constexpr LeAudioBroadcaster::AudioProfile default_profile =
LeAudioBroadcaster::AudioProfile::SONIFICATION;
-static constexpr LeAudioBroadcaster::Code default_code = {
+static constexpr BroadcastCode default_code = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
static const std::vector<uint8_t> default_metadata = {0x03, 0x02, 0x01, 0x00};
@@ -143,7 +143,7 @@ class BroadcasterTest : public Test {
uint8_t InstantiateBroadcast(
LeAudioBroadcaster::AudioProfile profile = default_profile,
std::vector<uint8_t> metadata = default_metadata,
- LeAudioBroadcaster::Code code = default_code) {
+ BroadcastCode code = default_code) {
uint8_t instance_id = LeAudioBroadcaster::kInstanceIdUndefined;
EXPECT_CALL(mock_broadcaster_callbacks_, OnBroadcastCreated(_, true))
.WillOnce(SaveArg<0>(&instance_id));
diff --git a/system/bta/le_audio/broadcaster/mock_state_machine.h b/system/bta/le_audio/broadcaster/mock_state_machine.h
index 738dd5d0df..69480ab2d7 100644
--- a/system/bta/le_audio/broadcaster/mock_state_machine.h
+++ b/system/bta/le_audio/broadcaster/mock_state_machine.h
@@ -97,7 +97,9 @@ class MockBroadcastStateMachine
MOCK_METHOD((void), RequestOwnAddress, (), (override));
MOCK_METHOD((RawAddress), GetOwnAddress, (), (override));
MOCK_METHOD((uint8_t), GetOwnAddressType, (), (override));
- MOCK_METHOD((bluetooth::le_audio::BroadcastId), GetBroadcastId, (),
+ MOCK_METHOD((std::optional<bluetooth::le_audio::BroadcastCode>),
+ GetBroadcastCode, (), (const override));
+ MOCK_METHOD((bluetooth::le_audio::BroadcastId const&), GetBroadcastId, (),
(const override));
MOCK_METHOD((void), UpdateBroadcastAnnouncement,
(le_audio::broadcaster::BasicAudioAnnouncementData announcement),
diff --git a/system/bta/le_audio/broadcaster/state_machine.cc b/system/bta/le_audio/broadcaster/state_machine.cc
index be69818e48..8b7b035626 100644
--- a/system/bta/le_audio/broadcaster/state_machine.cc
+++ b/system/bta/le_audio/broadcaster/state_machine.cc
@@ -101,10 +101,15 @@ class BroadcastStateMachineImpl : public BroadcastStateMachine {
return addr_type_;
}
- bluetooth::le_audio::BroadcastId GetBroadcastId() const override {
+ bluetooth::le_audio::BroadcastId const& GetBroadcastId() const override {
return sm_config_.broadcast_id;
}
+ std::optional<bluetooth::le_audio::BroadcastCode> GetBroadcastCode()
+ const override {
+ return sm_config_.broadcast_code;
+ }
+
void UpdateBroadcastAnnouncement(
BasicAudioAnnouncementData announcement) override {
std::vector<uint8_t> periodic_data;
diff --git a/system/bta/le_audio/broadcaster/state_machine.h b/system/bta/le_audio/broadcaster/state_machine.h
index 45d73c6b8d..5877a8c445 100644
--- a/system/bta/le_audio/broadcaster/state_machine.h
+++ b/system/bta/le_audio/broadcaster/state_machine.h
@@ -98,7 +98,7 @@ struct BroadcastStateMachineConfig {
uint8_t streaming_phy;
BroadcastCodecWrapper codec_wrapper;
BasicAudioAnnouncementData announcement;
- std::optional<LeAudioBroadcaster::Code> broadcast_code;
+ std::optional<bluetooth::le_audio::BroadcastCode> broadcast_code;
};
class BroadcastStateMachine : public StateMachine<5> {
@@ -144,7 +144,9 @@ class BroadcastStateMachine : public StateMachine<5> {
virtual void RequestOwnAddress() = 0;
virtual RawAddress GetOwnAddress() = 0;
virtual uint8_t GetOwnAddressType() = 0;
- virtual bluetooth::le_audio::BroadcastId GetBroadcastId() const = 0;
+ virtual std::optional<bluetooth::le_audio::BroadcastCode> GetBroadcastCode()
+ const = 0;
+ virtual bluetooth::le_audio::BroadcastId const& GetBroadcastId() const = 0;
virtual void UpdateBroadcastAnnouncement(
BasicAudioAnnouncementData announcement) = 0;
void SetMuted(bool muted) { is_muted_ = muted; };
diff --git a/system/bta/le_audio/broadcaster/state_machine_test.cc b/system/bta/le_audio/broadcaster/state_machine_test.cc
index a203bcab2b..f6cf786e58 100644
--- a/system/bta/le_audio/broadcaster/state_machine_test.cc
+++ b/system/bta/le_audio/broadcaster/state_machine_test.cc
@@ -218,11 +218,14 @@ class StateMachineTest : public Test {
std::future<uint8_t> instance_future =
instance_creation_promise_.get_future();
+ static uint8_t broadcast_id_lsb = 1;
+
auto codec_wrapper =
BroadcastCodecWrapper::getCodecConfigForProfile(profile);
pending_broadcasts_.push_back(BroadcastStateMachine::CreateInstance({
- .codec_wrapper = std::move(codec_wrapper),
+ .broadcast_id = {0, 0, broadcast_id_lsb++},
// .streaming_phy = ,
+ .codec_wrapper = std::move(codec_wrapper),
// .announcement = ,
// .broadcast_code = ,
}));
@@ -753,6 +756,19 @@ TEST_F(StateMachineTest, GetConfig) {
ASSERT_EQ(big_cfg->connection_handles.size(), num_channels);
}
+TEST_F(StateMachineTest, GetBroadcastId) {
+ EXPECT_CALL(*(sm_callbacks_.get()), OnStateMachineCreateStatus(_, true))
+ .Times(1);
+
+ uint8_t instance_id = InstantiateStateMachine();
+ ASSERT_EQ(broadcasts_[instance_id]->GetState(),
+ BroadcastStateMachine::State::CONFIGURED);
+
+ bluetooth::le_audio::BroadcastId const& broadcast_id =
+ broadcasts_[instance_id]->GetBroadcastId();
+ ASSERT_NE(bluetooth::le_audio::kBroadcastBroadcastIdInvalid, broadcast_id);
+}
+
TEST_F(StateMachineTest, AnnouncementUUIDs) {
std::vector<uint8_t> a_data;
std::vector<uint8_t> p_data;
diff --git a/system/include/hardware/bt_le_audio.h b/system/include/hardware/bt_le_audio.h
index c5cd87073a..44089b81b0 100644
--- a/system/include/hardware/bt_le_audio.h
+++ b/system/include/hardware/bt_le_audio.h
@@ -144,9 +144,10 @@ enum class BroadcastAudioProfile {
MEDIA,
};
-using BroadcastCode = std::array<uint8_t, 16>;
-using BroadcastId = std::array<uint8_t, 3>;
constexpr uint8_t kBroadcastAnnouncementBroadcastIdSize = 3;
+using BroadcastId = std::array<uint8_t, kBroadcastAnnouncementBroadcastIdSize>;
+constexpr BroadcastId kBroadcastBroadcastIdInvalid = {0, 0, 0};
+using BroadcastCode = std::array<uint8_t, 16>;
class LeAudioBroadcasterCallbacks {
public: