diff options
Diffstat (limited to 'framework/java/android/bluetooth')
13 files changed, 3041 insertions, 1140 deletions
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java index f0a6c4a48a..b2c6a5e87f 100644 --- a/framework/java/android/bluetooth/BluetoothDevice.java +++ b/framework/java/android/bluetooth/BluetoothDevice.java @@ -1139,6 +1139,8 @@ public final class BluetoothDevice implements Parcelable, Attributable { ADDRESS_TYPE_PUBLIC, /** Address is either resolvable, non-resolvable or static.*/ ADDRESS_TYPE_RANDOM, + /** Address type is unknown or unavailable **/ + ADDRESS_TYPE_UNKNOWN, } ) public @interface AddressType {} @@ -1147,6 +1149,8 @@ public final class BluetoothDevice implements Parcelable, Attributable { public static final int ADDRESS_TYPE_PUBLIC = 0; /** Address is either resolvable, non-resolvable or static. */ public static final int ADDRESS_TYPE_RANDOM = 1; + /** Address type is unknown or unavailable **/ + public static final int ADDRESS_TYPE_UNKNOWN = 0xFFFF; private static final String NULL_MAC_ADDRESS = "00:00:00:00:00:00"; diff --git a/framework/java/android/bluetooth/BluetoothLeAudioCodecConfigMetadata.java b/framework/java/android/bluetooth/BluetoothLeAudioCodecConfigMetadata.java new file mode 100644 index 0000000000..4ca657c449 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeAudioCodecConfigMetadata.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 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.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class representing the codec specific config metadata information defined in the Basic Audio + * Profile. + * + * @hide + */ +@SystemApi +public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable { + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + + private final long mAudioLocation; + private final byte[] mRawMetadata; + + private BluetoothLeAudioCodecConfigMetadata(long audioLocation, byte[] rawMetadata) { + mAudioLocation = audioLocation; + mRawMetadata = rawMetadata; + } + + /** + * Get the audio location information as defined in the Generic Audio section of Bluetooth + * Assigned numbers. + * + * @return configured audio location, -1 if this metadata does not exist + * @hide + */ + @SystemApi + public long getAudioLocation() { + return mAudioLocation; + } + + /** + * Get the raw bytes of stream metadata in Bluetooth LTV format. + * + * Bluetooth LTV format for stream metadata is defined in the Generic Audio + * section of <a href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned Numbers</a>, + * including metadata that was not covered by the getter methods in this class. + * + * @return raw bytes of stream metadata in Bluetooth LTV format + * @hide + */ + @SystemApi + public @NonNull byte[] getRawMetadata() { + return mRawMetadata; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mAudioLocation); + if (mRawMetadata != null) { + out.writeInt(mRawMetadata.length); + out.writeByteArray(mRawMetadata); + } else { + out.writeInt(-1); + } + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeAudioCodecConfigMetadata} from + * parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeAudioCodecConfigMetadata> CREATOR = + new Parcelable.Creator<BluetoothLeAudioCodecConfigMetadata>() { + @NonNull + public BluetoothLeAudioCodecConfigMetadata createFromParcel(@NonNull Parcel in) { + long audioLocation = in.readLong(); + int rawMetadataLen = in.readInt(); + byte[] rawMetadata; + if (rawMetadataLen != -1) { + rawMetadata = new byte[rawMetadataLen]; + in.readByteArray(rawMetadata); + } else { + rawMetadata = new byte[0]; + } + return new BluetoothLeAudioCodecConfigMetadata(audioLocation, rawMetadata); + } + + public @NonNull BluetoothLeAudioCodecConfigMetadata[] newArray(int size) { + return new BluetoothLeAudioCodecConfigMetadata[size]; + } + }; + + /** + * Construct a {@link BluetoothLeAudioCodecConfigMetadata} from raw bytes. + * + * The byte array will be parsed and values for each getter will be populated + * + * Raw metadata cannot be set using builder in order to maintain raw bytes and getter value + * consistency + * + * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format + * @return parsed {@link BluetoothLeAudioCodecConfigMetadata} object + * @throws IllegalArgumentException if <var>rawBytes</var> is null or when the raw bytes cannot + * be parsed to build the object + * @hide + */ + @SystemApi + public static @NonNull BluetoothLeAudioCodecConfigMetadata fromRawBytes( + @NonNull byte[] rawBytes) { + if (rawBytes == null) { + throw new IllegalArgumentException("Raw bytes cannot be null"); + } + return null; + } + + /** + * Builder for {@link BluetoothLeAudioCodecConfigMetadata}. + * @hide + */ + @SystemApi + public static final class Builder { + private long mAudioLocation = UNKNOWN_VALUE_PLACEHOLDER; + private byte[] mRawMetadata = null; + + /** + * Create an empty builder. + * @hide + */ + @SystemApi + public Builder() {} + + /** + * Create a builder with copies of information from original object. + * + * @param original original object + * @hide + */ + @SystemApi + public Builder(@NonNull BluetoothLeAudioCodecConfigMetadata original) { + mAudioLocation = original.getAudioLocation(); + mRawMetadata = original.getRawMetadata(); + } + + /** + * Set the audio location information as defined in the Generic Audio section of Bluetooth + * Assigned numbers. + * + * @param audioLocation configured audio location, -1 if does not exist + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setAudioLocation(long audioLocation) { + mAudioLocation = audioLocation; + return this; + } + + /** + * Build {@link BluetoothLeAudioCodecConfigMetadata}. + * + * @return constructed {@link BluetoothLeAudioCodecConfigMetadata} + * @throws IllegalArgumentException if the object cannot be built + * @hide + */ + @SystemApi + public @NonNull BluetoothLeAudioCodecConfigMetadata build() { + if (mRawMetadata == null) { + mRawMetadata = new byte[0]; + } + return new BluetoothLeAudioCodecConfigMetadata(mAudioLocation, mRawMetadata); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java b/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java new file mode 100644 index 0000000000..47ab6983e0 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 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.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class representing the media metadata information defined in the Basic Audio Profile. + * + * @hide + */ +@SystemApi +public final class BluetoothLeAudioContentMetadata implements Parcelable { + private final String mProgramInfo; + private final String mLanguage; + private final byte[] mRawMetadata; + + private BluetoothLeAudioContentMetadata(String programInfo, String language, + byte[] rawMetadata) { + mProgramInfo = programInfo; + mLanguage = language; + mRawMetadata = rawMetadata; + } + + /** + * Get the title and/or summary of Audio Stream content in UTF-8 format. + * + * @return title and/or summary of Audio Stream content in UTF-8 format, null if this metadata + * does not exist + * @hide + */ + @SystemApi + public @Nullable String getProgramInfo() { + return mProgramInfo; + } + + /** + * Get language of the audio stream in 3-byte, lower case language code as defined in ISO 639-3. + * + * @return ISO 639-3 formatted language code, null if this metadata does not exist + * @hide + */ + @SystemApi + public @Nullable String getLanguage() { + return mLanguage; + } + + /** + * Get the raw bytes of stream metadata in Bluetooth LTV format as defined in the Generic Audio + * section of <a href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned Numbers</a>, + * including metadata that was not covered by the getter methods in this class + * + * @return raw bytes of stream metadata in Bluetooth LTV format + */ + public @NonNull byte[] getRawMetadata() { + return mRawMetadata; + } + + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mProgramInfo); + out.writeString(mLanguage); + out.writeInt(mRawMetadata.length); + out.writeByteArray(mRawMetadata); + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeAudioContentMetadata} from parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeAudioContentMetadata> CREATOR = + new Parcelable.Creator<BluetoothLeAudioContentMetadata>() { + public @NonNull BluetoothLeAudioContentMetadata createFromParcel( + @NonNull Parcel in) { + final String programInfo = in.readString(); + final String language = in.readString(); + final int rawMetadataLength = in.readInt(); + byte[] rawMetadata = new byte[rawMetadataLength]; + in.readByteArray(rawMetadata); + return new BluetoothLeAudioContentMetadata(programInfo, language, rawMetadata); + } + + public @NonNull BluetoothLeAudioContentMetadata[] newArray(int size) { + return new BluetoothLeAudioContentMetadata[size]; + } + }; + + /** + * Construct a {@link BluetoothLeAudioContentMetadata} from raw bytes. + * + * The byte array will be parsed and values for each getter will be populated + * + * Raw metadata cannot be set using builder in order to maintain raw bytes and getter value + * consistency + * + * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format + * @return parsed {@link BluetoothLeAudioContentMetadata} object + * @throws IllegalArgumentException if <var>rawBytes</var> is null or when the raw bytes cannot + * be parsed to build the object + * @hide + */ + @SystemApi + public static @NonNull BluetoothLeAudioContentMetadata fromRawBytes(@NonNull byte[] rawBytes) { + if (rawBytes == null) { + throw new IllegalArgumentException("Raw bytes cannot be null"); + } + return null; + } + + /** + * Builder for {@link BluetoothLeAudioContentMetadata}. + * @hide + */ + @SystemApi + public static final class Builder { + private String mProgramInfo = null; + private String mLanguage = null; + private byte[] mRawMetadata = null; + + /** + * Create an empty builder + * + * @hide + */ + @SystemApi + public Builder() {} + + /** + * Create a builder with copies of information from original object. + * + * @param original original object + * @hide + */ + @SystemApi + public Builder(@NonNull BluetoothLeAudioContentMetadata original) { + mProgramInfo = original.getProgramInfo(); + mLanguage = original.getLanguage(); + mRawMetadata = original.getRawMetadata(); + } + + /** + * Set the title and/or summary of Audio Stream content in UTF-8 format. + * + * @param programInfo title and/or summary of Audio Stream content in UTF-8 format, null + * if this metadata does not exist + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setProgramInfo(@Nullable String programInfo) { + mProgramInfo = programInfo; + return this; + } + + /** + * Set language of the audio stream in 3-byte, lower case language code as defined in + * ISO 639-3. + * + * @return ISO 639-3 formatted language code, null if this metadata does not exist + * @hide + */ + @SystemApi + public @NonNull Builder setLanguage(@Nullable String language) { + mLanguage = language; + return this; + } + + /** + * Build {@link BluetoothLeAudioContentMetadata}. + * + * @return constructed {@link BluetoothLeAudioContentMetadata} + * @throws IllegalArgumentException if the object cannot be built + * @hide + */ + @SystemApi + public @NonNull BluetoothLeAudioContentMetadata build() { + if (mRawMetadata == null) { + mRawMetadata = new byte[0]; + } + return new BluetoothLeAudioContentMetadata(mProgramInfo, mLanguage, mRawMetadata); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcast.java b/framework/java/android/bluetooth/BluetoothLeBroadcast.java index fed9f911d5..4537cc6f97 100644 --- a/framework/java/android/bluetooth/BluetoothLeBroadcast.java +++ b/framework/java/android/bluetooth/BluetoothLeBroadcast.java @@ -16,271 +16,413 @@ package android.bluetooth; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.content.Context; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; /** - * This class provides the public APIs to control the Bluetooth LE Broadcast Source profile. + * This class provides the public APIs to control the BAP Broadcast Source profile. * - * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast - * Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} - * to get the BluetoothLeBroadcast proxy object. + * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast Source + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothLeBroadcast + * proxy object. * * @hide */ -public final class BluetoothLeBroadcast implements BluetoothProfile { +@SystemApi +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; /** - * Constants used by the LE Audio Broadcast profile for the Broadcast state - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = { - LE_AUDIO_BROADCAST_STATE_DISABLED, - LE_AUDIO_BROADCAST_STATE_ENABLING, - LE_AUDIO_BROADCAST_STATE_ENABLED, - LE_AUDIO_BROADCAST_STATE_DISABLING, - LE_AUDIO_BROADCAST_STATE_PLAYING, - LE_AUDIO_BROADCAST_STATE_NOT_PLAYING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastState {} - - /** - * Indicates that LE Audio Broadcast mode is currently disabled - * + * Interface for receiving events related to Broadcast Source * @hide */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10; + @SystemApi + public interface Callback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST, + BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, + BluetoothStatusCodes.REASON_SYSTEM_POLICY, + BluetoothStatusCodes.ERROR_HARDWARE_GENERIC, + BluetoothStatusCodes.ERROR_BAD_PARAMETERS, + BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES, + BluetoothStatusCodes.ERROR_LE_BROADCAST_INVALID_CODE, + BluetoothStatusCodes.ERROR_LE_BROADCAST_INVALID_BROADCAST_ID, + BluetoothStatusCodes.ERROR_LE_CONTENT_METADATA_INVALID_PROGRAM_INFO, + BluetoothStatusCodes.ERROR_LE_CONTENT_METADATA_INVALID_LANGUAGE, + BluetoothStatusCodes.ERROR_LE_CONTENT_METADATA_INVALID_OTHER, + }) + @interface Reason {} - /** - * Indicates that LE Audio Broadcast mode is being enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11; + /** + * Callback invoked when broadcast is started, but audio may not be playing. + * + * Caller should wait for + * {@link #onBroadcastMetadataChanged(int, BluetoothLeBroadcastMetadata)} + * for the updated metadata + * + * @param reason for broadcast start + * @param broadcastId as defined by the Basic Audio Profile + * @hide + */ + @SystemApi + void onBroadcastStarted(@Reason int reason, int broadcastId); - /** - * Indicates that LE Audio Broadcast mode is currently enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12; - /** - * Indicates that LE Audio Broadcast mode is being disabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13; + /** + * Callback invoked when broadcast failed to start + * + * @param reason for broadcast start failure + * @hide + */ + @SystemApi + void onBroadcastStartFailed(@Reason int reason); - /** - * Indicates that an LE Audio Broadcast mode is currently playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14; + /** + * Callback invoked when broadcast is stopped + * + * @param reason for broadcast stop + * @hide + */ + @SystemApi + void onBroadcastStopped(@Reason int reason, int broadcastId); - /** - * Indicates that LE Audio Broadcast is currently not playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15; + /** + * Callback invoked when broadcast failed to stop + * + * @param reason for broadcast stop failure + * @hide + */ + @SystemApi + void onBroadcastStopFailed(@Reason int reason); - /** - * Constants used by the LE Audio Broadcast profile for encryption key length - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = { - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT, - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioEncryptionKeyLength {} + /** + * Callback invoked when broadcast audio is playing + * + * @param reason for playback start + * @param broadcastId as defined by the Basic Audio Profile + * @hide + */ + @SystemApi + void onPlaybackStarted(@Reason int reason, int broadcastId); - /** - * Indicates that the LE Audio Broadcast encryption key size is 32 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16; + /** + * Callback invoked when broadcast audio is not playing + * + * @param reason for playback stop + * @param broadcastId as defined by the Basic Audio Profile + * @hide + */ + @SystemApi + void onPlaybackStopped(@Reason int reason, int broadcastId); - /** - * Indicates that the LE Audio Broadcast encryption key size is 128 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17; + /** + * Callback invoked when encryption is enabled + * + * @param reason for encryption enable + * @param broadcastId as defined by the Basic Audio Profile + * @hide + */ + @SystemApi + void onBroadcastUpdated(@Reason int reason, int broadcastId); - /** - * Interface for receiving events related to broadcasts - */ - public interface Callback { /** - * Called when broadcast state has changed + * Callback invoked when Broadcast Source failed to update * - * @param prevState broadcast state before the change - * @param newState broadcast state after the change + * @param reason for update failure + * @param broadcastId as defined by the Basic Audio Profile + * @hide */ - @LeAudioBroadcastState - void onBroadcastStateChange(int prevState, int newState); + @SystemApi + void onBroadcastUpdateFailed(int reason, int broadcastId); + /** - * Called when encryption key has been updated + * Callback invoked when Broadcast Source metadata is updated * - * @param success true if the key was updated successfully, false otherwise + * @param metadata updated Broadcast Source metadata + * @param broadcastId as defined by the Basic Audio Profile + * @hide */ - void onEncryptionKeySet(boolean success); + @SystemApi + void onBroadcastMetadataChanged(int broadcastId, + @NonNull BluetoothLeBroadcastMetadata metadata); } /** - * Create a BluetoothLeBroadcast proxy object for interacting with the local - * LE Audio Broadcast Source service. + * Create a BluetoothLeBroadcast proxy object for interacting with the local LE Audio Broadcast + * Source service. * + * @param context for to operate this API class + * @param listener listens for service callbacks across binder * @hide */ - /*package*/ BluetoothLeBroadcast(Context context, - BluetoothProfile.ServiceListener listener) { - } + /*package*/ BluetoothLeBroadcast(Context context, BluetoothProfile.ServiceListener listener) {} /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException + * Not supported since LE Audio Broadcasts do not establish a connection. * * @hide */ @Override - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public int getConnectionState(@NonNull BluetoothDevice device) { + throw new UnsupportedOperationException("LE Audio Broadcasts are not connection-oriented."); } /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException + * Not supported since LE Audio Broadcasts do not establish a connection. * * @hide */ @Override - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( + @NonNull int[] states) { + throw new UnsupportedOperationException("LE Audio Broadcasts are not connection-oriented."); } /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException + * Not supported since LE Audio Broadcasts do not establish a connection. * * @hide */ @Override - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull List<BluetoothDevice> getConnectedDevices() { + throw new UnsupportedOperationException("LE Audio Broadcasts are not connection-oriented."); } /** - * Enable LE Audio Broadcast mode. + * Register a {@link Callback} that will be invoked during the operation of this profile. * - * Generates a new broadcast ID and enables sending of encrypted or unencrypted - * isochronous PDUs + * Repeated registration of the same <var>callback</var> object will have no effect after + * the first call to this method, even when the <var>executor</var> is different. API caller + * would have to call {@link #unregisterCallback(Callback)} with + * the same callback object before registering it again. * + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link Callback} + * @throws IllegalArgumentException if a null executor, sink, or callback is given * @hide */ - public int enableBroadcastMode() { - if (DBG) log("enableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void registerCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + if (executor == null) { + throw new IllegalArgumentException("executor cannot be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + log("registerCallback"); + throw new UnsupportedOperationException("Not Implemented"); } /** - * Disable LE Audio Broadcast mode. + * Unregister the specified {@link Callback} + * <p>The same {@link Callback} object used when calling + * {@link #registerCallback(Executor, Callback)} must be used. + * + * <p>Callbacks are automatically unregistered when application process goes away * + * @param callback user implementation of the {@link Callback} + * @throws IllegalArgumentException when callback is null or when no callback is registered * @hide */ - public int disableBroadcastMode() { - if (DBG) log("disableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void unregisterCallback(@NonNull Callback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + log("unregisterCallback"); + throw new UnsupportedOperationException("Not Implemented"); } /** - * Get the current LE Audio broadcast state + * Start broadcasting to nearby devices using <var>broadcastCode</var> and + * <var>contentMetadata</var> + * + * Encryption will be enabled when <var>broadcastCode</var> is not null. + * + * <p>As defined in Volume 3, Part C, Section 3.2.6 of Bluetooth Core Specification, Version + * 5.3, Broadcast Code is used to encrypt a broadcast audio stream. + * <p>It must be a UTF-8 string that has at least 4 octets and should not exceed 16 octets. * + * If the provided <var>broadcastCode</var> is non-null and does not meet the above + * requirements, encryption will fail to enable with reason code + * {@link BluetoothStatusCodes#ERROR_LE_BROADCAST_INVALID_CODE} + * + * Caller can set content metadata such as program information string in + * <var>contentMetadata</var> + * + * On success, {@link Callback#onBroadcastStarted(int, int)} will be invoked with + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} reason code. + * On failure, {@link Callback#onBroadcastStartFailed(int)} will be invoked with reason code. + * + * In particular, when the number of Broadcast Sources reaches + * {@link #getMaximumNumberOfBroadcast()}, this method will fail with + * {@link BluetoothStatusCodes#ERROR_LOCAL_NOT_ENOUGH_RESOURCES} + * + * After broadcast is started, + * {@link Callback#onBroadcastMetadataChanged(int, BluetoothLeBroadcastMetadata)} + * will be invoked to expose the latest Broadcast Group metadata that can be shared out of band + * to set up Broadcast Sink without scanning. + * + * Alternatively, one can also get the latest Broadcast Source meta via + * {@link #getAllBroadcastMetadata()} + * + * @param contentMetadata metadata for the default Broadcast subgroup + * @param broadcastCode Encryption will be enabled when <var>broadcastCode</var> is not null + * @throws IllegalArgumentException if <var>contentMetadata</var> is null + * @throws IllegalStateException if callback was not registered * @hide */ - @LeAudioBroadcastState - public int getBroadcastState() { - if (DBG) log("getBroadcastState"); - return LE_AUDIO_BROADCAST_STATE_DISABLED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void startBroadcast(@NonNull BluetoothLeAudioContentMetadata contentMetadata, + @Nullable byte[] broadcastCode) { + if (DBG) log("startBroadcasting"); } /** - * Enable LE Audio broadcast encryption + * Update the broadcast with <var>broadcastId</var> with new <var>contentMetadata</var> * - * @param keyLength if useExisting is true, this specifies the length of the key that should - * be generated - * @param useExisting true, if an existing key should be used - * false, if a new key should be generated + * On success, {@link Callback#onBroadcastUpdated(int, int)} will be invoked with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. + * On failure, {@link Callback#onBroadcastUpdateFailed(int, int)} will be invoked with reason + * code * + * @param broadcastId broadcastId as defined by the Basic Audio Profile + * @param contentMetadata updated metadata for the default Broadcast subgroup + * @throws IllegalStateException if callback was not registered * @hide */ - @LeAudioEncryptionKeyLength - public int enableEncryption(boolean useExisting, int keyLength) { - if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void updateBroadcast(int broadcastId, + @NonNull BluetoothLeAudioContentMetadata contentMetadata) { + } /** - * Disable LE Audio broadcast encryption + * Stop broadcasting. * - * @param removeExisting true, if the existing key should be removed - * false, otherwise + * On success, {@link Callback#onBroadcastStopped(int, int)} will be invoked with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} and the <var>broadcastId</var> + * On failure, {@link Callback#onBroadcastStopFailed(int)} will be invoked with reason code * + * @param broadcastId as defined by the Basic Audio Profile + * @throws IllegalStateException if callback was not registered * @hide */ - public int disableEncryption(boolean removeExisting) { - if (DBG) log("disableEncryption removeExisting=" + removeExisting); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void stopBroadcast(int broadcastId) { + if (DBG) log("disableBroadcastMode"); } /** - * Enable or disable LE Audio broadcast encryption - * - * @param key use the provided key if non-null, generate a new key if null - * @param keyLength 0 if encryption is disabled, 4 bytes (low security), - * 16 bytes (high security) + * Return true if audio is being broadcasted on the Broadcast Source as identified by the + * <var>broadcastId</var> * + * @param broadcastId as defined in the Basic Audio Profile + * @return true if audio is being broadcasted * @hide */ - @LeAudioEncryptionKeyLength - public int setEncryptionKey(byte[] key, int keyLength) { - if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public boolean isPlaying(int broadcastId) { + return false; } - /** - * Get the encryption key that was set before - * - * @return encryption key as a byte array or null if no encryption key was set + * Get {@link BluetoothLeBroadcastMetadata} for all Broadcast Groups currently running on + * this device * + * @return list of {@link BluetoothLeBroadcastMetadata} * @hide */ - public byte[] getEncryptionKey() { - if (DBG) log("getEncryptionKey"); - return null; + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() { + return Collections.emptyList(); } + /** + * Get the maximum number of broadcast groups supported on this device + * @return maximum number of broadcast groups supported on this device + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getMaximumNumberOfBroadcast() { + return 1; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void close() throws Exception {} + private static void log(String msg) { Log.d(TAG, msg); } diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java new file mode 100644 index 0000000000..81f5d18c8b --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java @@ -0,0 +1,738 @@ +/* + * Copyright 2021 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.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresBluetoothLocationPermission; +import android.bluetooth.annotations.RequiresBluetoothScanPermission; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; +import android.content.Context; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * This class provides the public APIs for the LE Audio Broadcast Assistant role, which implements + * client side control points for Broadcast Audio Scan Service (BASS). + * + * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast + * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. + * This is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT + * server that is part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on + * the Broadcast Assistant. + * + * <p>Once a GATT connection is established between the BASS client and the BASS server, the + * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast + * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the + * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the + * Assistant about changes such as addition and removal of Broadcast Sources. + * + * In the context of this class, BASS server will be addressed as Broadcast Sink and BASS client + * will be addressed as Broadcast Assistant. + * + * <p>BluetoothLeBroadcastAssistant is a proxy object for controlling the Broadcast Assistant + * service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the + * BluetoothLeBroadcastAssistant proxy object. + * + * @hide + */ +@SystemApi +public final class BluetoothLeBroadcastAssistant implements BluetoothProfile { + private static final String TAG = "BluetoothLeBroadcastAssistant"; + private static final boolean DBG = true; + + /** + * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources + * is offloaded to a Broadcast Assistant. + * + * @hide + */ + @SystemApi + public interface Callback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST, + BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, + BluetoothStatusCodes.REASON_REMOTE_REQUEST, + BluetoothStatusCodes.REASON_SYSTEM_POLICY, + BluetoothStatusCodes.ERROR_HARDWARE_GENERIC, + BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION, + BluetoothStatusCodes.ERROR_BAD_PARAMETERS, + BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR, + BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES, + BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID, + BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE, + BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, + }) + @interface Reason {} + + /** + * Callback invoked when the implementation started searching for nearby Broadcast Sources. + * + * @param reason reason code on why search has started + * @hide + */ + @SystemApi + void onSearchStarted(@Reason int reason); + + /** + * Callback invoked when the implementation failed to start searching for nearby broadcast + * sources. + * + * @param reason reason for why search failed to start + * @hide + */ + @SystemApi + void onSearchStartFailed(@Reason int reason); + + /** + * Callback invoked when the implementation stopped searching for nearby Broadcast Sources. + * + * @param reason reason code on why search has stopped + * @hide + */ + @SystemApi + void onSearchStopped(@Reason int reason); + + /** + * Callback invoked when the implementation failed to stop searching for nearby broadcast + * sources. + * + * @param reason for why search failed to start + * @hide + */ + @SystemApi + void onSearchStopFailed(@Reason int reason); + + /** + * Callback invoked when a new Broadcast Source is found together with the + * {@link BluetoothLeBroadcastMetadata}. + * + * @param source {@link BluetoothLeBroadcastMetadata} representing a Broadcast Source + * @hide + */ + @SystemApi + void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source); + + /** + * Callback invoked when a new Broadcast Source has been successfully added to the + * Broadcast Sink. + * + * Broadcast audio stream may not have been started after this callback, the caller need + * to monitor + * {@link #onReceiveStateChanged(BluetoothDevice, int, BluetoothLeBroadcastReceiveState)} + * to see if synchronization with Broadcast Source is successful + * + * When <var>isGroupOp</var> is true when + * {@link #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, boolean)} + * is called, each Broadcast Sink device in the coordinated set will trigger and individual + * update + * + * A new source could be added by the Broadcast Sink itself or other Broadcast Assistants + * connected to the Broadcast Sink and in this case the reason code will be + * {@link BluetoothStatusCodes#REASON_REMOTE_REQUEST} + * + * @param sink Broadcast Sink device on which a new Broadcast Source has been added + * @param sourceId source ID as defined in the BASS specification + * @param reason reason of source addition + * @hide + */ + @SystemApi + void onSourceAdded(@NonNull BluetoothDevice sink, @Reason int sourceId, + @Reason int reason); + + /** + * Callback invoked when the new Broadcast Source failed to be added to the Broadcast Sink. + * + * @param sink Broadcast Sink device on which a new Broadcast Source has been added + * @param source metadata representation of the Broadcast Source + * @param reason reason why the addition has failed + * @hide + */ + @SystemApi + void onSourceAddFailed(@NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata source, @Reason int reason); + + /** + * Callback invoked when an existing Broadcast Source within a Broadcast Sink has been + * modified. + * + * Actual state after the modification will be delivered via the next + * {@link Callback#onReceiveStateChanged(BluetoothDevice, int, + * BluetoothLeBroadcastReceiveState)} + * callback. + * + * A source could be modified by the Broadcast Sink itself or other Broadcast Assistants + * connected to the Broadcast Sink and in this case the reason code will be + * {@link BluetoothStatusCodes#REASON_REMOTE_REQUEST} + * + * @param sink Broadcast Sink device on which a Broadcast Source has been modified + * @param sourceId source ID as defined in the BASS specification + * @param reason reason of source modification + * @hide + */ + @SystemApi + void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); + + /** + * Callback invoked when the Broadcast Assistant failed to modify an existing Broadcast + * Source on a Broadcast Sink. + * + * @param sink Broadcast Sink device on which a Broadcast Source has been modified + * @param sourceId source ID as defined in the BASS specification + * @param reason reason why the modification has failed + * @hide + */ + @SystemApi + void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); + + /** + * Callback invoked when a Broadcast Source has been successfully removed from the + * Broadcast Sink. + * + * No more update for the source ID via + * {@link Callback#onReceiveStateChanged(BluetoothDevice, int, + * BluetoothLeBroadcastReceiveState)} + * after this callback. + * + * A source could be removed by the Broadcast Sink itself or other Broadcast Assistants + * connected to the Broadcast Sink and in this case the reason code will be + * {@link BluetoothStatusCodes#REASON_REMOTE_REQUEST} + * + * @param sink Broadcast Sink device from which a Broadcast Source has been removed + * @param sourceId source ID as defined in the BASS specification + * @param reason reason why the Broadcast Source was removed + * @hide + */ + @SystemApi + void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); + + /** + * Callback invoked when the Broadcast Assistant failed to remove an existing Broadcast + * Source on a Broadcast Sink. + * + * @param sink Broadcast Sink device on which a Broadcast Source was to be removed + * @param sourceId source ID as defined in the BASS specification + * @param reason reason why the modification has failed + * @hide + */ + @SystemApi + void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); + + /** + * Callback invoked when the Broadcast Receive State information of a Broadcast Sink device + * changes. + * + * @param sink BASS server device that is also a Broadcast Sink device + * @param sourceId source ID as defined in the BASS specification + * @param state latest state information between the Broadcast Sink and a Broadcast Source + * @hide + */ + @SystemApi + void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId, + @NonNull BluetoothLeBroadcastReceiveState state); + } + + /** + * Intent used to broadcast the change in connection state of devices via Broadcast Audio Scan + * Service (BASS). Please note that in a coordinated set, each set member will connect via BASS + * individually. Group operations on a single set member will propagate to the entire set. + * + * For example, in the binaural case, there will be two different LE devices for the left and + * right side and each device will have their own connection state changes. If both devices + * belongs to on Coordinated Set, group operation on one of them will affect both devices. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.action.CONNECTION_STATE_CHANGED"; + + /** + * Create a new instance of an LE Audio Broadcast Assistant. + * + * @hide + */ + /*package*/ BluetoothLeBroadcastAssistant( + @NonNull Context context, @NonNull ServiceListener listener) { + } + + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @Override + public @BluetoothProfile.BtProfileState int getConnectionState(@NonNull BluetoothDevice sink) { + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @Override + public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( + @NonNull int[] states) { + return Collections.emptyList(); + } + + /** + * {@inheritDoc} + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @Override + @NonNull + public List<BluetoothDevice> getConnectedDevices() { + return Collections.emptyList(); + } + + /** + * Set connection policy of the profile. + * + * <p> The device should already be paired. Connection policy can be one of { + * @link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + return false; + } + + /** + * Get the connection policy of the profile. + * + * <p> The connection policy can be any of: + * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } + + /** + * Register a {@link Callback} that will be invoked during the operation of this profile. + * + * Repeated registration of the same <var>callback</var> object will have no effect after the + * first call to this method, even when the <var>executor</var> is different. Caller would have + * to call {@link #unregisterCallback(Callback)} with the same callback object before + * registering it again. + * + * @param executor an {@link Executor} to execute given callback + * @param callback user implementation of the {@link Callback} + * @throws IllegalArgumentException if a null executor, sink, or callback is given + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void registerCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull Callback callback) { + if (executor == null) { + throw new IllegalArgumentException("executor cannot be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + log("registerCallback"); + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Unregister the specified {@link Callback}. + * + * <p>The same {@link Callback} object used when calling + * {@link #registerCallback(Executor, Callback)} must be used. + * + * <p>Callbacks are automatically unregistered when application process goes away. + * + * @param callback user implementation of the {@link Callback} + * @throws IllegalArgumentException when callback is null or when no callback is registered + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void unregisterCallback(@NonNull Callback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + log("unregisterCallback"); + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Search for LE Audio Broadcast Sources on behalf of all devices connected via Broadcast Audio + * Scan Service, filtered by <var>filters</var>. + * + * On success, {@link Callback#onSearchStarted(int)} will be called with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. + * + * On failure, {@link Callback#onSearchStartFailed(int)} will be called with reason code + * + * The implementation will also synchronize with discovered Broadcast Sources and get their + * metadata before passing the Broadcast Source metadata back to the application using {@link + * Callback#onSourceFound(BluetoothLeBroadcastMetadata)}. + * + * Please disconnect the Broadcast Sink's BASS server by calling + * {@link #setConnectionPolicy(BluetoothDevice, int)} with + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} if you do not want the Broadcast Sink + * to receive notifications about this search before calling this method. + * + * App must also have + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} + * permission in order to get results. + * + * <var>filters</var> will be AND'ed with internal filters in the implementation and + * {@link ScanSettings} will be managed by the implementation. + * + * @param filters {@link ScanFilter}s for finding exact Broadcast Source, if no filter is + * needed, please provide an empty list instead + * @throws IllegalArgumentException when <var>filters</var> argument is null + * @throws IllegalStateException when no callback is registered + * @hide + */ + @SystemApi + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void startSearchingForSources(@NonNull List<ScanFilter> filters) { + log("searchForBroadcastSources"); + if (filters == null) { + throw new IllegalArgumentException("filters can be empty, but not null"); + } + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Stops an ongoing search for nearby Broadcast Sources. + * + * On success, {@link Callback#onSearchStopped(int)} will be called with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. + * On failure, {@link Callback#onSearchStopFailed(int)} will be called with reason code + * + * @throws IllegalStateException if callback was not registered + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void stopSearchingForSources() { + log("stopSearchingForSources:"); + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Return true if a search has been started by this application. + * + * @return true if a search has been started by this application + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public boolean isSearchInProgress() { + return false; + } + + /** + * Add a Broadcast Source to the Broadcast Sink. + * + * Caller can modify <var>sourceMetadata</var> before using it in this method to set a + * Broadcast Code, to select a different Broadcast Channel in a Broadcast Source such as channel + * with a different language, and so on. What can be modified is listed in the documentation of + * {@link #modifySource(BluetoothDevice, int, BluetoothLeBroadcastMetadata)} and can also be + * modified after a source is added. + * + * On success, {@link Callback#onSourceAdded(BluetoothDevice, int, int)} will be invoked with + * a <var>sourceID</var> assigned by the Broadcast Sink with reason code + * {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. However, this callback only indicates + * that the Broadcast Sink has allocated resource to receive audio from the Broadcast Source, + * and audio stream may not have started. The caller should then wait for + * {@link Callback#onReceiveStateChanged(BluetoothDevice, int, + * BluetoothLeBroadcastReceiveState)} + * callback to monitor the encryption and audio sync state. + * + * Note that wrong broadcast code will not prevent the source from being added to the Broadcast + * Sink. Caller should modify the current source to correct the broadcast code. + * + * On failure, + * {@link Callback#onSourceAddFailed(BluetoothDevice, BluetoothLeBroadcastMetadata, int)} + * will be invoked with the same <var>source</var> metadata and reason code + * + * When too many sources was added to Broadcast sink, error + * {@link BluetoothStatusCodes#ERROR_REMOTE_NOT_ENOUGH_RESOURCES} will be delivered. In this + * case, check the capacity of Broadcast sink via + * {@link #getMaximumSourceCapacity(BluetoothDevice)} and the current list of sources via + * {@link #getAllSources(BluetoothDevice)}. + * + * Some sources might be added by other Broadcast Assistants and hence was not + * in {@link Callback#onSourceAdded(BluetoothDevice, int, int)} callback, but will be updated + * via {@link Callback#onReceiveStateChanged(BluetoothDevice, int, + * BluetoothLeBroadcastReceiveState)} + * + * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp is + * set to true, the Broadcast Source will be added to each sink in the coordinated set and a + * separate {@link Callback#onSourceAdded} callback will be invoked for each member of the + * coordinated set. + * + * <p>The <var>isGroupOp</var> option is sticky. This means that subsequent operations using + * {@link #modifySource(BluetoothDevice, int, BluetoothLeBroadcastMetadata)} and + * {@link #removeSource(BluetoothDevice, int)} will act on all devices in the same coordinated + * set for the <var>sink</var> and <var>sourceID</var> pair until the <var>sourceId</var> is + * removed from the <var>sink</var> by any Broadcast role (could be another remote device). + * + * <p>When <var>isGroupOp</var> is true, if one Broadcast Sink in a coordinated set + * disconnects from this Broadcast Assistant or lost the Broadcast Source, this Broadcast + * Assistant will try to add it back automatically to make sure the whole coordinated set + * is in the same state. + * + * @param sink Broadcast Sink to which the Broadcast Source should be added + * @param sourceMetadata Broadcast Source metadata to be added to the Broadcast Sink + * @param isGroupOp {@code true} if Application wants to perform this operation for all + * coordinated set members throughout this session. Otherwise, caller + * would have to add, modify, and remove individual set members. + * @throws IllegalArgumentException if <var>sink</var> or <var>source</var> are null + * @throws IllegalStateException if callback was not registered + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void addSource(@NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) { + log("addBroadcastSource: " + sourceMetadata + " on " + sink); + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Modify the Broadcast Source information on a Broadcast Sink. + * + * One can modify {@link BluetoothLeBroadcastMetadata#getBroadcastCode()} if + * {@link BluetoothLeBroadcastReceiveState#getBigEncryptionState()} returns + * {@link BluetoothLeBroadcastReceiveState#BIG_ENCRYPTION_STATE_BAD_CODE} or + * {@link BluetoothLeBroadcastReceiveState#BIG_ENCRYPTION_STATE_CODE_REQUIRED} + * + * One can modify {@link BluetoothLeBroadcastMetadata#getPaSyncInterval()} if the Broadcast + * Assistant received updated information. + * + * One can modify {@link BluetoothLeBroadcastChannel#isSelected()} to select different broadcast + * channel to listen to (one per {@link BluetoothLeBroadcastSubgroup} or set + * {@link BluetoothLeBroadcastSubgroup#isNoChannelPreference()} to leave the choice to the + * Broadcast Sink. + * + * One can modify {@link BluetoothLeBroadcastSubgroup#getContentMetadata()} if the subgroup + * metadata changes and the Broadcast Sink need help updating the metadata from Broadcast + * Assistant. + * + * Each of the above modifications can be accepted or rejected by the Broadcast Assistant + * implement and/or the Broadcast Sink. + * + * <p>On success, {@link Callback#onSourceModified(BluetoothDevice, int, int)} will be invoked + * with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. + * + * <p>On failure, {@link Callback#onSourceModifyFailed(BluetoothDevice, int, int)} will be + * invoked with reason code. + * + * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp + * is set to true during + * {@link #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, boolean)}, + * the source will be modified on each sink in the coordinated set and a separate + * {@link Callback#onSourceModified(BluetoothDevice, int, int)} callback will be invoked for + * each member of the coordinated set. + * + * @param sink Broadcast Sink to which the Broadcast Source should be updated + * @param sourceId source ID as delivered in + * {@link Callback#onSourceAdded(BluetoothDevice, int, int)} + * @param updatedMetadata updated Broadcast Source metadata to be updated on the Broadcast Sink + * @throws IllegalStateException if callback was not registered + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void modifySource(@NonNull BluetoothDevice sink, int sourceId, + @NonNull BluetoothLeBroadcastMetadata updatedMetadata) { + log("updateBroadcastSource: " + updatedMetadata + " on " + sink); + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Removes the Broadcast Source from a Broadcast Sink. + * + * <p>On success, {@link Callback#onSourceRemoved(BluetoothDevice, int, int)} will be invoked + * with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. + * + * <p>On failure, {@link Callback#onSourceRemoveFailed(BluetoothDevice, int, int)} will be + * invoked with reason code. + + * + * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp is + * set to true during + * {@link #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, boolean)}, + * the source will be removed from each sink in the coordinated set and a separate + * {@link Callback#onSourceRemoved(BluetoothDevice, int, int)} callback will be invoked for + * each member of the coordinated set. + * + * @param sink Broadcast Sink from which a Broadcast Source should be removed + * @param sourceId source ID as delivered in + * {@link Callback#onSourceAdded(BluetoothDevice, int, int)} + * @throws IllegalArgumentException when the <var>sink</var> is null + * @throws IllegalStateException if callback was not registered + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public void removeSource(@NonNull BluetoothDevice sink, int sourceId) { + log("removeBroadcastSource: " + sourceId + " from " + sink); + return; + } + + + /** + * Get information about all Broadcast Sources that a Broadcast Sink knows about. + * + * @param sink Broadcast Sink from which to get all Broadcast Sources + * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} + * stored in the Broadcast Sink + * @throws IllegalArgumentException when <var>sink</var> is null + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources( + @NonNull BluetoothDevice sink) { + return Collections.emptyList(); + } + + /** + * Get maximum number of sources that can be added to this Broadcast Sink. + * + * @param sink Broadcast Sink device + * @return maximum number of sources that can be added to this Broadcast Sink + * @throws IllegalArgumentException when <var>sink</var> is null + * @hide + */ + @SystemApi + public int getMaximumSourceCapacity(@NonNull BluetoothDevice sink) { + return 0; + } + + private static void log(@NonNull String msg) { + if (DBG) { + Log.d(TAG, msg); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java deleted file mode 100644 index c6d161ed51..0000000000 --- a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2021 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.annotation.IntDef; -import android.annotation.NonNull; -import android.bluetooth.le.ScanResult; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources is - * offloaded to a Broadcast Assistant. - * - * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast - * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This - * is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is - * part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast - * Assistant. - * - * <p>Once a GATT connection is established between the BASS client and the BASS server, the - * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast - * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the - * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the - * Assistant about changes such as addition and removal of Broadcast Sources. - * - * @hide - */ -public abstract class BluetoothLeBroadcastAssistantCallback { - - /** - * Broadcast Audio Scan Service (BASS) codes returned by a BASS Server - * - * @hide - */ - @IntDef( - prefix = "BASS_STATUS_", - value = { - BASS_STATUS_SUCCESS, - BASS_STATUS_FAILURE, - BASS_STATUS_INVALID_GATT_HANDLE, - BASS_STATUS_TXN_TIMEOUT, - BASS_STATUS_INVALID_SOURCE_ID, - BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, - BASS_STATUS_INVALID_SOURCE_SELECTED, - BASS_STATUS_SOURCE_UNAVAILABLE, - BASS_STATUS_DUPLICATE_ADDITION, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BassStatus {} - - public static final int BASS_STATUS_SUCCESS = 0x00; - public static final int BASS_STATUS_FAILURE = 0x01; - public static final int BASS_STATUS_INVALID_GATT_HANDLE = 0x02; - public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; - - public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; - public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; - public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; - public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; - public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; - public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; - public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothProfile.STATE_CONNECTED, - BluetoothProfile.STATE_CONNECTING, - BluetoothProfile.STATE_DISCONNECTED, - BluetoothProfile.STATE_DISCONNECTING - }) - public @interface ConnectionStateValues {} - - /** - * Callback invoked when the connection state for an LE Audio Broadcast Sink changes - */ - public void onConnectionStateChange(@ConnectionStateValues int prevState, - @ConnectionStateValues int newState) {} - - /** - * Callback invoked when a new LE Audio Broadcast Source is found. - * - * @param result {@link ScanResult} scan result representing a Broadcast Source - */ - public void onSourceFound(@NonNull ScanResult result) {} - - /** - * Callback invoked when the Broadcast Assistant synchronizes with Periodic Advertisements (PAs) - * of an LE Audio Broadcast Source. - * - * @param source the selected Broadcast Source - */ - public void onSourceSelected( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when the Broadcast Assistant loses synchronization with an LE Audio - * Broadcast Source. - * - * @param source the Broadcast Source with which synchronization was lost - */ - public void onSourceLost( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when a new LE Audio Broadcast Source has been successfully added to the Scan - * Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device on which a new Broadcast Source has been added - * @param source the added Broadcast Source - */ - public void onSourceAdded( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an existing LE Audio Broadcast Source within a remote Scan Delegator - * has been updated. - * - * @param sink Scan Delegator device on which a Broadcast Source has been updated - * @param source the updated Broadcast Source - */ - public void onSourceUpdated( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an LE Audio Broadcast Source has been successfully removed from the - * Scan Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device from which a Broadcast Source has been removed - * @param source the removed Broadcast Source - */ - public void onSourceRemoved( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} -} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java b/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java new file mode 100644 index 0000000000..6addc062e3 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java @@ -0,0 +1,206 @@ +/* + * 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.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains the Broadcast Isochronous Channel level information as defined in the BASE + * structure of the Basic Audio Profile. + * + * @hide + */ +@SystemApi +public final class BluetoothLeBroadcastChannel implements Parcelable { + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + + private final boolean mIsSelected; + private final int mChannelIndex; + private final BluetoothLeAudioCodecConfigMetadata mCodecMetadata; + + private BluetoothLeBroadcastChannel(boolean isSelected, int channelIndex, + BluetoothLeAudioCodecConfigMetadata codecMetadata) { + mIsSelected = isSelected; + mChannelIndex = channelIndex; + mCodecMetadata = codecMetadata; + } + + /** + * Return true if the channel is selected by Broadcast Assistant for the Broadcast Sink. + * + * Used by Broadcast Assistant and Sink, but not Broadcast Source + * + * @return true if the channel is selected by Broadcast Assistant for the Broadcast Sink + * @hide + */ + @SystemApi + public boolean isSelected() { + return mIsSelected; + } + + /** + * Get the Broadcast Isochronous Channel index of this Broadcast Channel. + * + * @return Broadcast Isochronous Channel index + * @hide + */ + @SystemApi + public int getChannelIndex() { + return mChannelIndex; + } + + /** + * Return the codec specific configuration for this Broadcast Channel. + * + * @return codec specific configuration for this Broadcast Channel + * @hide + */ + @SystemApi + public @NonNull BluetoothLeAudioCodecConfigMetadata getCodecMetadata() { + return mCodecMetadata; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeBoolean(mIsSelected); + out.writeInt(mChannelIndex); + out.writeTypedObject(mCodecMetadata, 0); + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastChannel} from parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastChannel> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastChannel>() { + public @NonNull BluetoothLeBroadcastChannel createFromParcel(@NonNull Parcel in) { + BluetoothLeBroadcastChannel.Builder + builder = new BluetoothLeBroadcastChannel.Builder(); + builder.setSelected(in.readBoolean()); + builder.setChannelIndex(in.readInt()); + builder.setCodecMetadata( + in.readTypedObject(BluetoothLeAudioCodecConfigMetadata.CREATOR)); + return builder.build(); + } + + public @NonNull BluetoothLeBroadcastChannel[] newArray(int size) { + return new BluetoothLeBroadcastChannel[size]; + } + }; + + /** + * Builder for {@link BluetoothLeBroadcastChannel}. + * @hide + */ + @SystemApi + public static final class Builder { + private boolean mIsSelected = false; + private int mChannelIndex = UNKNOWN_VALUE_PLACEHOLDER; + private BluetoothLeAudioCodecConfigMetadata mCodecMetadata = null; + + /** + * Create an empty builder. + * @hide + */ + @SystemApi + public Builder() {} + + /** + * Create a builder with copies of information from original object. + * + * @param original original object + * @hide + */ + @SystemApi + public Builder(@NonNull BluetoothLeBroadcastChannel original) { + mIsSelected = original.isSelected(); + mChannelIndex = original.getChannelIndex(); + mCodecMetadata = original.getCodecMetadata(); + } + + /** + * Set if the channel is selected by Broadcast Assistant for the Broadcast Sink. + * + * Used by Broadcast Assistant and Sink, but not Broadcast Source + * + * @param isSelected true if the channel is selected by Broadcast Assistant for the + * Broadcast Sink + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setSelected(boolean isSelected) { + mIsSelected = isSelected; + return this; + } + + /** + * Set the Broadcast Isochronous Channel index of this Broadcast Channel. + * + * @return Broadcast Isochronous Channel index + * @hide + */ + @SystemApi + public @NonNull Builder setChannelIndex(int channelIndex) { + mChannelIndex = channelIndex; + return this; + } + + /** + * Set the codec specific configuration for this Broadcast Channel. + * + * @param codecMetadata codec specific configuration for this Broadcast Channel + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setCodecMetadata( + @NonNull BluetoothLeAudioCodecConfigMetadata codecMetadata) { + mCodecMetadata = codecMetadata; + return this; + } + + /** + * Build {@link BluetoothLeBroadcastChannel}. + * + * @return constructed {@link BluetoothLeBroadcastChannel} + * @throws IllegalArgumentException if the object cannot be built + * @hide + */ + @SystemApi + public @NonNull BluetoothLeBroadcastChannel build() { + return new BluetoothLeBroadcastChannel(mIsSelected, mChannelIndex, mCodecMetadata); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java b/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java new file mode 100644 index 0000000000..1810818c98 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java @@ -0,0 +1,486 @@ +/* + * 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.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a Broadcast Source group and the associated information that is needed + * by Broadcast Audio Scan Service (BASS) to set up a Broadcast Sink. + * + * <p>For example, an LE Audio Broadcast Sink can use the information contained within an instance + * of this class to synchronize with an LE Audio Broadcast group in order to listen to audio from + * Broadcast subgroup using one or more Broadcast Channels. + * + * @hide + */ +@SystemApi +public final class BluetoothLeBroadcastMetadata implements Parcelable { + // Information needed for adding broadcast Source + + // Optional: Identity address type + private final @BluetoothDevice.AddressType int mSourceAddressType; + // Optional: Must use identity address + private final BluetoothDevice mSourceDevice; + private final int mSourceAdvertisingSid; + private final int mBroadcastId; + private final int mPaSyncInterval; + private final boolean mIsEncrypted; + private final byte[] mBroadcastCode; + + // BASE structure + + // See Section 7 for description. Range: 0x000000 – 0xFFFFFF Units: μs + //All other values: RFU + private final int mPresentationDelayMicros; + // Number of subgroups used to group BISes present in the BIG + //Shall be at least 1, as defined by Rule 1 + // Sub group info numSubGroup = mSubGroups.length + private final List<BluetoothLeBroadcastSubgroup> mSubgroups; + + private BluetoothLeBroadcastMetadata(int sourceAddressType, + BluetoothDevice sourceDevice, int sourceAdvertisingSid, int broadcastId, + int paSyncInterval, boolean isEncrypted, byte[] broadcastCode, int presentationDelay, + List<BluetoothLeBroadcastSubgroup> subgroups) { + mSourceAddressType = sourceAddressType; + mSourceDevice = sourceDevice; + mSourceAdvertisingSid = sourceAdvertisingSid; + mBroadcastId = broadcastId; + mPaSyncInterval = paSyncInterval; + mIsEncrypted = isEncrypted; + mBroadcastCode = broadcastCode; + mPresentationDelayMicros = presentationDelay; + mSubgroups = subgroups; + } + + /** + * Get the address type of the Broadcast Source. + * + * Can be either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}, + * {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} + * + * @return address type of the Broadcast Source + * @hide + */ + @SystemApi + public @BluetoothDevice.AddressType int getSourceAddressType() { + return mSourceAddressType; + } + + /** + * Get the MAC address of the Broadcast Source, which can be Public Device Address, + * Random Device Address, Public Identity Address or Random (static) Identity Address. + * + * @return MAC address of the Broadcast Source + * @hide + */ + @SystemApi + public @Nullable BluetoothDevice getSourceDevice() { + return mSourceDevice; + } + + /** + * Get Advertising_SID subfield of the ADI field of the AUX_ADV_IND PDU or the + * LL_PERIODIC_SYNC_IND containing the SyncInfo that points to the PA transmitted by the + * Broadcast Source. + * + * @return 1-byte long Advertising_SID of the Broadcast Source + * @hide + */ + @SystemApi + public int getSourceAdvertisingSid() { + return mSourceAdvertisingSid; + } + + /** + * Broadcast_ID of the Broadcast Source. + * + * @return 3-byte long Broadcast_ID of the Broadcast Source + * @hide + */ + @SystemApi + public int getBroadcastId() { + return mBroadcastId; + } + + /** + * Indicated that Periodic Advertising Sync interval is unknown. + * @hide + */ + @SystemApi + public static final int PA_SYNC_INTERVAL_UNKNOWN = 0xFFFF; + + /** + * Get Periodic Advertising Sync interval of the broadcast Source. + * + * @return Periodic Advertising Sync interval of the broadcast Source, + * {@link #PA_SYNC_INTERVAL_UNKNOWN} if unknown + * @hide + */ + @SystemApi + public int getPaSyncInterval() { + return mPaSyncInterval; + } + + /** + * Return true if the Broadcast Source is encrypted. + * + * @return true if the Broadcast Source is encrypted + * @hide + */ + @SystemApi + public boolean isEncrypted() { + return mIsEncrypted; + } + + /** + * Get the Broadcast Code currently set for this Broadcast Source. + * + * Only needed when encryption is enabled + * + * <p>As defined in Volume 3, Part C, Section 3.2.6 of Bluetooth Core Specification, Version + * 5.3, Broadcast Code is used to encrypt a broadcast audio stream. + * <p>It must be a UTF-8 string that has at least 4 octets and should not exceed 16 octets. + * + * @return Broadcast Code currently set for this Broadcast Source, null if code is not required + * or code is currently unknown + * @hide + */ + @SystemApi + public @Nullable byte[] getBroadcastCode() { + return mBroadcastCode; + } + + /** + * Get the overall presentation delay in microseconds of this Broadcast Source. + * + * Presentation delay is defined in Section 7 of the Basic Audio Profile. + * + * @return presentation delay of this Broadcast Source in microseconds + * @hide + */ + @SystemApi + public @IntRange(from = 0, to = 0xFFFFFF) int getPresentationDelayMicros() { + return mPresentationDelayMicros; + } + + /** + * Get available subgroups in this broadcast source. + * + * @return list of subgroups in this broadcast source, which should contain at least one + * subgroup for each Broadcast Source + * @hide + */ + @SystemApi + public @NonNull List<BluetoothLeBroadcastSubgroup> getSubgroups() { + return mSubgroups; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSourceAddressType); + if (mSourceDevice != null) { + out.writeInt(1); + out.writeTypedObject(mSourceDevice, 0); + } else { + // zero indicates missing mSourceDevice + out.writeInt(0); + } + out.writeInt(mSourceAdvertisingSid); + out.writeInt(mBroadcastId); + out.writeInt(mPaSyncInterval); + out.writeBoolean(mIsEncrypted); + if (mBroadcastCode != null) { + out.writeInt(mBroadcastCode.length); + out.writeByteArray(mBroadcastCode); + } else { + // -1 indicates missing broadcast code + out.writeInt(-1); + } + out.writeInt(mPresentationDelayMicros); + out.writeTypedList(mSubgroups); + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastMetadata} from parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastMetadata> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastMetadata>() { + public @NonNull BluetoothLeBroadcastMetadata createFromParcel(@NonNull Parcel in) { + Builder builder = new Builder(); + final int sourceAddressType = in.readInt(); + final int deviceExist = in.readInt(); + BluetoothDevice sourceDevice = null; + if (deviceExist == 1) { + sourceDevice = in.readTypedObject(BluetoothDevice.CREATOR); + } + builder.setSourceDevice(sourceDevice, sourceAddressType); + builder.setSourceAdvertisingSid(in.readInt()); + builder.setBroadcastId(in.readInt()); + builder.setPaSyncInterval(in.readInt()); + builder.setEncrypted(in.readBoolean()); + final int codeLen = in.readInt(); + byte[] broadcastCode = null; + if (codeLen != -1) { + broadcastCode = new byte[codeLen]; + if (codeLen > 0) { + in.readByteArray(broadcastCode); + } + } + builder.setBroadcastCode(broadcastCode); + builder.setPresentationDelayMicros(in.readInt()); + final List<BluetoothLeBroadcastSubgroup> subgroups = new ArrayList<>(); + in.readTypedList(subgroups, BluetoothLeBroadcastSubgroup.CREATOR); + for (BluetoothLeBroadcastSubgroup subgroup : subgroups) { + builder.addSubgroup(subgroup); + } + return builder.build(); + } + + public @NonNull BluetoothLeBroadcastMetadata[] newArray(int size) { + return new BluetoothLeBroadcastMetadata[size]; + } + }; + + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + + /** + * Builder for {@link BluetoothLeBroadcastMetadata}. + * @hide + */ + @SystemApi + public static final class Builder { + private @BluetoothDevice.AddressType int mSourceAddressType = + BluetoothDevice.ADDRESS_TYPE_UNKNOWN; + private BluetoothDevice mSourceDevice = null; + private int mSourceAdvertisingSid = UNKNOWN_VALUE_PLACEHOLDER; + private int mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER; + private int mPaSyncInterval = UNKNOWN_VALUE_PLACEHOLDER; + private boolean mIsEncrypted = false; + private byte[] mBroadcastCode = null; + private int mPresentationDelayMicros = UNKNOWN_VALUE_PLACEHOLDER; + private List<BluetoothLeBroadcastSubgroup> mSubgroups = new ArrayList<>(); + + /** + * Create an empty builder. + * @hide + */ + @SystemApi + public Builder() {} + + /** + * Create a builder with copies of information from original object. + * + * @param original original object + * @hide + */ + @SystemApi + public Builder(@NonNull BluetoothLeBroadcastMetadata original) { + mSourceAddressType = original.getSourceAddressType(); + mSourceDevice = original.getSourceDevice(); + mSourceAdvertisingSid = original.getSourceAdvertisingSid(); + mBroadcastId = original.getBroadcastId(); + mPaSyncInterval = original.getPaSyncInterval(); + mIsEncrypted = original.isEncrypted(); + mBroadcastCode = original.getBroadcastCode(); + mPresentationDelayMicros = original.getPresentationDelayMicros(); + mSubgroups = original.getSubgroups(); + } + + + /** + * Set the address type and MAC address of the Broadcast Source. + * + * Address type can be either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}, + * {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} + * + * MAC address can be Public Device Address, Random Device Address, Public Identity Address + * or Random (static) Identity Address + * + * @param sourceDevice source advertiser address + * @param sourceAddressType source advertiser address type + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setSourceDevice(@Nullable BluetoothDevice sourceDevice, + @BluetoothDevice.AddressType int sourceAddressType) { + mSourceDevice = sourceDevice; + mSourceAddressType = sourceAddressType; + return this; + } + + /** + * Set Advertising_SID that is a subfield of the ADI field of the AUX_ADV_IND PDU or the + * LL_PERIODIC_SYNC_IND containing the SyncInfo that points to the PA transmitted by the + * Broadcast Source. + * + * @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setSourceAdvertisingSid(int sourceAdvertisingSid) { + mSourceAdvertisingSid = sourceAdvertisingSid; + return this; + } + + /** + * Set the Broadcast_ID of the Broadcast Source. + * + * @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setBroadcastId(int broadcastId) { + mBroadcastId = broadcastId; + return this; + } + + /** + * Set Periodic Advertising Sync interval of the broadcast Source. + * + * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, + * {@link #PA_SYNC_INTERVAL_UNKNOWN} if unknown + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setPaSyncInterval(int paSyncInterval) { + mPaSyncInterval = paSyncInterval; + return this; + } + + /** + * Set whether the Broadcast Source should be encrypted. + * + * When setting up a Broadcast Source, if <var>isEncrypted</var> is true while + * <var>broadcastCode</var> is null, the implementation will automatically generate + * a Broadcast Code + * + * @param isEncrypted whether the Broadcast Source is encrypted + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setEncrypted(boolean isEncrypted) { + mIsEncrypted = isEncrypted; + return this; + } + + /** + * Set the Broadcast Code currently set for this Broadcast Source. + * + * Only needed when encryption is enabled + * + * <p>As defined in Volume 3, Part C, Section 3.2.6 of Bluetooth Core Specification, Version + * 5.3, Broadcast Code is used to encrypt a broadcast audio stream. + * <p>It must be a UTF-8 string that has at least 4 octets and should not exceed 16 octets. + * + * @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not + * required + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setBroadcastCode(@Nullable byte[] broadcastCode) { + mBroadcastCode = broadcastCode; + return this; + } + + /** + * Set the overall presentation delay in microseconds of this Broadcast Source. + * + * Presentation delay is defined in Section 7 of the Basic Audio Profile. + * + * @param presentationDelayMicros presentation delay of this Broadcast Source in + * microseconds + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setPresentationDelayMicros( + @IntRange(from = 0, to = 0xFFFFFF) int presentationDelayMicros) { + mPresentationDelayMicros = presentationDelayMicros; + return this; + } + + /** + * Add a subgroup to this broadcast source. + * + * @param subgroup {@link BluetoothLeBroadcastSubgroup} that contains a subgroup's metadata + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder addSubgroup(@NonNull BluetoothLeBroadcastSubgroup subgroup) { + mSubgroups.add(subgroup); + return this; + } + + /** + * Clear subgroup list so that one can reset the builder after create it from an existing + * object. + * + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder clearSubgroup() { + mSubgroups.clear(); + return this; + } + + /** + * Build {@link BluetoothLeBroadcastMetadata}. + * + * @return {@link BluetoothLeBroadcastMetadata} + * @throws IllegalArgumentException if the object cannot be built + * @hide + */ + @SystemApi + public @NonNull BluetoothLeBroadcastMetadata build() { + return new BluetoothLeBroadcastMetadata(mSourceAddressType, mSourceDevice, + mSourceAdvertisingSid, mBroadcastId, mPaSyncInterval, mIsEncrypted, + mBroadcastCode, mPresentationDelayMicros, mSubgroups); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java b/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java new file mode 100644 index 0000000000..bab17ee797 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java @@ -0,0 +1,449 @@ +/* + * 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.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link BluetoothLeBroadcastReceiveState} is used by the BASS server to expose information + * about a Broadcast Source. + * + * It represents the current synchronization state of the server to + * a PA and/or a BIG containing one or more subgroups containing one or more BISes + * transmitted by that Broadcast Source. The Broadcast Receive State characteristic is also + * used to inform clients whether the server has detected that the BIS is encrypted, whether + * the server requires a Broadcast_Code, and whether the server is decrypting the BIS. + * + * @hide + */ +@SystemApi +public final class BluetoothLeBroadcastReceiveState implements Parcelable { + /** + * Periodic Advertising Synchronization state. + * + * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast + * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast + * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the + * Periodic Advertising Synchronization Transfer (PAST) procedure. + * + * @hide + */ + @IntDef(prefix = "PA_SYNC_STATE_", + value = { + PA_SYNC_STATE_IDLE, + PA_SYNC_STATE_SYNCINFO_REQUEST, + PA_SYNC_STATE_SYNCHRONIZED, + PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE, + PA_SYNC_STATE_NO_PAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PaSyncState {} + + /** + * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) + * + * @hide + */ + @SystemApi + public static final int PA_SYNC_STATE_IDLE = 0; + + /** + * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the + * Periodic Advertisements (PA). + * + * <p>This is also known as scan delegation or scan offloading. + * + * @hide + */ + @SystemApi + public static final int PA_SYNC_STATE_SYNCINFO_REQUEST = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). + * + * @hide + */ + @SystemApi + public static final int PA_SYNC_STATE_SYNCHRONIZED = 2; + + /** + * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements + * (PA). + * + * @hide + */ + @SystemApi + public static final int PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE = 3; + + /** + * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements + * (PA) using the Periodic Advertisements Synchronization Transfer (PAST) procedure. + * + * @hide + */ + @SystemApi + public static final int PA_SYNC_STATE_NO_PAST = 4; + + /** + * Indicates that the Broadcast Sink synchronization state is invalid. + * + * @hide + */ + public static final int PA_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef( + prefix = "BIG_ENCRYPTION_STATE_", + value = { + BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, + BIG_ENCRYPTION_STATE_CODE_REQUIRED, + BIG_ENCRYPTION_STATE_DECRYPTING, + BIG_ENCRYPTION_STATE_BAD_CODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BigEncryptionState {} + + /** + * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream from a + * Broadcast Source + * + * @hide + */ + @SystemApi + public static final int BIG_ENCRYPTION_STATE_NOT_ENCRYPTED = 0; + + /** + * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with an audio stream + * from a Broadcast Source, which was not provided when the audio stream from the Broadcast + * Source was added. + * + * @hide + */ + @SystemApi + public static final int BIG_ENCRYPTION_STATE_CODE_REQUIRED = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream from a + * Broadcast Source. + * + * @hide + */ + @SystemApi + public static final int BIG_ENCRYPTION_STATE_DECRYPTING = 2; + + /** + * Indicates that the Broadcast Sink is unable to decrypt an audio stream from a Broadcast + * Source due to an incorrect Broadcast Code. + * + * @hide + */ + @SystemApi + public static final int BIG_ENCRYPTION_STATE_BAD_CODE = 3; + + /** + * Indicates that the Broadcast Sink encryption state is invalid. + * + * @hide + */ + public static final int BIG_ENCRYPTION_STATE_INVALID = 0xFFFF; + + private final int mSourceId; + private final @BluetoothDevice.AddressType int mSourceAddressType; + private final BluetoothDevice mSourceDevice; + private final int mSourceAdvertisingSid; + private final int mBroadcastId; + private final @PaSyncState int mPaSyncState; + private final @BigEncryptionState int mBigEncryptionState; + private final byte[] mBadCode; + private final int mNumSubgroups; + private final List<Long> mBisSyncState; + private final List<BluetoothLeAudioContentMetadata> mSubgroupMetadata; + + /** + * Constructor to create a read-only {@link BluetoothLeBroadcastReceiveState} instance. + * + * @hide + */ + public BluetoothLeBroadcastReceiveState(int sourceId, int sourceAddressType, + BluetoothDevice sourceDevice, int sourceAdvertisingSid, int broadcastId, + int paSyncState, int bigEncryptionState, byte[] badCode, int numSubgroups, + List<Long> bisSyncState, + List<BluetoothLeAudioContentMetadata> subgroupMetadata) { + mSourceId = sourceId; + mSourceAddressType = sourceAddressType; + mSourceDevice = sourceDevice; + mSourceAdvertisingSid = sourceAdvertisingSid; + mBroadcastId = broadcastId; + mPaSyncState = paSyncState; + mBigEncryptionState = bigEncryptionState; + mBadCode = badCode; + mNumSubgroups = numSubgroups; + mBisSyncState = bisSyncState; + mSubgroupMetadata = subgroupMetadata; + } + + /** + * Get the source ID assigned by the BASS server + * + * Shall be unique for each instance of the Broadcast Receive State characteristic exposed by + * the server + * + * @return source ID assigned by the BASS server + * @hide + */ + @SystemApi + public @IntRange(from = 0x00, to = 0xFF) int getSourceId() { + return mSourceId; + } + + /** + * Get the address type of the Broadcast Source + * + * Can be either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} or + * {@link BluetoothDevice#ADDRESS_TYPE_RANDOM + * + * @return address type of the Broadcast Source + * @hide + */ + @SystemApi + public @BluetoothDevice.AddressType int getSourceAddressType() { + return mSourceAddressType; + } + + /** + * Get the MAC address of the Broadcast Source, which can be Public Device Address, + * Random Device Address, Public Identity Address or Random (static) Identity Address + * + * @return MAC address of the Broadcast Source + * @hide + */ + @SystemApi + public @NonNull BluetoothDevice getSourceDevice() { + return mSourceDevice; + } + + /** + * Get Advertising_SID subfield of the ADI field of the AUX_ADV_IND PDU or the + * LL_PERIODIC_SYNC_IND containing the SyncInfo that points to the PA transmitted by the + * Broadcast Source. + * + * @return 1-byte long Advertising_SID of the Broadcast Source + * @hide + */ + @SystemApi + public int getSourceAdvertisingSid() { + return mSourceAdvertisingSid; + } + + /** + * Broadcast_ID of the Broadcast Source + * + * @return 3-byte long Broadcast_ID of the Broadcast Source + * @hide + */ + @SystemApi + public int getBroadcastId() { + return mBroadcastId; + } + + /** + * Get the Periodic Advertisement synchronization state between the Broadcast Sink and the + * Broadcast source + * + * Possible values are {@link #PA_SYNC_STATE_IDLE}, {@link #PA_SYNC_STATE_SYNCINFO_REQUEST}, + * {@link #PA_SYNC_STATE_SYNCHRONIZED}, {@link #PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE}, + * {@link #PA_SYNC_STATE_NO_PAST} + * + * @return Periodic Advertisement synchronization state + * @hide + */ + @SystemApi + public @PaSyncState int getPaSyncState() { + return mPaSyncState; + } + + /** + * Get the encryption state of a Broadcast Isochronous Group (BIG) + * + * Possible values are {@link #BIG_ENCRYPTION_STATE_NOT_ENCRYPTED}, + * {@link #BIG_ENCRYPTION_STATE_CODE_REQUIRED}, {@link #BIG_ENCRYPTION_STATE_DECRYPTING}, + * {@link #BIG_ENCRYPTION_STATE_DECRYPTING}, and {@link #BIG_ENCRYPTION_STATE_BAD_CODE} + * + * @return encryption state of a Broadcast Isochronous Group (BIG) + * @hide + */ + @SystemApi + public @BigEncryptionState int getBigEncryptionState() { + return mBigEncryptionState; + } + + /** + * If {@link #getBigEncryptionState()} returns {@link #BIG_ENCRYPTION_STATE_BAD_CODE}, this + * method returns the value of the incorrect 16-octet Broadcast Code that fails to decrypt + * an audio stream from a Broadcast Source. + * + * @return 16-octet Broadcast Code, or null if {@link #getBigEncryptionState()} does not return + * {@link #BIG_ENCRYPTION_STATE_BAD_CODE} + * @hide + */ + @SystemApi + public @Nullable byte[] getBadCode() { + return mBadCode; + } + + /** + * Get number of Broadcast subgroups being added to this sink + * + * @return number of Broadcast subgroups being added to this sink + */ + public int getNumSubgroups() { + return mNumSubgroups; + } + + /** + * Get a list of bitfield on whether a Broadcast Isochronous Stream (BIS) is synchronized + * between the sink and source + * + * The number of items in the returned list is the same as {@link #getNumSubgroups()}. For each + * subgroup, at most 31 BISes are available and their synchronization state is indicated by its + * bit value at the particular offset (i.e. Bit 0-30 = BIS_index[1-31]) + * + * For example, if (BisSyncState & 0b1 << 5) != 0, BIS 5 is synchronized between source and sync + * + * There is a special case, 0xFFFFFFFF to indicate Broadcast Sink failed to synchronize to + * a particular subgroup + * + * @return a list of bitfield on whether a Broadcast Isochronous Stream (BIS) is synchronized + * between the sink and source + * @hide + */ + @SystemApi + public @NonNull List<Long> getBisSyncState() { + return mBisSyncState; + } + + /** + * Get metadata for every subgroup added to this Broadcast Sink + * + * The number of items in the returned list is the same as {@link #getNumSubgroups()}. + * + * @return metadata for every subgroup added to this Broadcast Sink + * @hide + */ + @SystemApi + public @NonNull List<BluetoothLeAudioContentMetadata> getSubgroupMetadata() { + return mSubgroupMetadata; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSourceId); + out.writeInt(mSourceAddressType); + out.writeTypedObject(mSourceDevice, 0); + out.writeInt(mSourceAdvertisingSid); + out.writeInt(mBroadcastId); + out.writeInt(mPaSyncState); + out.writeInt(mBigEncryptionState); + + if (mBadCode != null) { + out.writeInt(mBadCode.length); + out.writeByteArray(mBadCode); + } else { + // -1 indicates that there is no "bad broadcast code" + out.writeInt(-1); + } + out.writeInt(mNumSubgroups); + out.writeList(mBisSyncState); + out.writeTypedList(mSubgroupMetadata); + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastReceiveState} from parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastReceiveState> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastReceiveState>() { + public @NonNull BluetoothLeBroadcastReceiveState createFromParcel( + @NonNull Parcel in) { + final int sourceId = in.readInt(); + final int sourceAddressType = in.readInt(); + final BluetoothDevice sourceDevice = + in.readTypedObject(BluetoothDevice.CREATOR); + final int sourceAdvertisingSid = in.readInt(); + final int broadcastId = in.readInt(); + final int paSyncState = in.readInt(); + final int bigEncryptionState = in.readInt(); + final int badCodeLen = in.readInt(); + byte[] badCode = null; + + if (badCodeLen != -1) { + badCode = new byte[badCodeLen]; + if (badCodeLen > 0) { + in.readByteArray(badCode); + } + } + final byte numSubGroups = in.readByte(); + final List<Long> bisSyncState = + in.readArrayList(Long.class.getClassLoader(), Long.class); + final List<BluetoothLeAudioContentMetadata> subgroupMetadata = + new ArrayList<>(); + in.readTypedList(subgroupMetadata, BluetoothLeAudioContentMetadata.CREATOR); + + return new BluetoothLeBroadcastReceiveState( + sourceId, + sourceAddressType, + sourceDevice, + sourceAdvertisingSid, + broadcastId, + paSyncState, + bigEncryptionState, + badCode, + numSubGroups, + bisSyncState, + subgroupMetadata); + } + + public @NonNull BluetoothLeBroadcastReceiveState[] newArray(int size) { + return new BluetoothLeBroadcastReceiveState[size]; + } + }; +} diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java b/framework/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java deleted file mode 100644 index cb47280acc..0000000000 --- a/framework/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java +++ /dev/null @@ -1,788 +0,0 @@ -/* - * Copyright 2021 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.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class represents an LE Audio Broadcast Source and the associated information that is needed - * by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator. - * - * <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information - * contained within an instance of this class to synchronize with an LE Audio Broadcast Source in - * order to listen to a Broadcast Audio Stream. - * - * <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast - * Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast - * sources, this information needs to be communicated to the BASS Server residing within the Scan - * Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization - * Transfer (PAST) procedure. This procedure uses information contained within an instance of this - * class. - * - * @hide - */ -public final class BluetoothLeBroadcastSourceInfo implements Parcelable { - private static final String TAG = "BluetoothLeBroadcastSourceInfo"; - private static final boolean DBG = true; - - /** - * Constants representing Broadcast Source address types - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_", - value = { - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSourceAddressType {} - - /** - * Represents a public address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0; - - /** - * Represents a random address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1; - - /** - * Represents an invalid address used by an LE Audio Broadcast Seurce - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF; - - /** - * Periodic Advertising Synchronization state - * - * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast - * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast - * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the - * Periodic Advertising Synchronizaton Transfer (PAST) procedure. - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkPaSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0; - - /** - * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the - * Periodic Advertisements (PA). - * - * <p>This is also known as scan delegation or scan offloading. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2; - - /** - * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements - * (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3; - - /** - * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements - * (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4; - - /** - * Indicates that the Broadcast Sink synchornization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkAudioSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; - - /** - * Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; - - /** - * Indicates that the Broadcast Sink audio synchronization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkEncryptionState {} - - /** - * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0; - - /** - * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio - * stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2; - - /** - * Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect - * Broadcast Code - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3; - - /** - * Indicates that the Broadcast Sink encryption state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF; - - /** - * Represents an invalid LE Audio Broadcast Source ID - * - * @hide - */ - public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00; - - /** - * Represents an invalid Broadcast ID of a Broadcast Source - * - * @hide - */ - public static final int INVALID_BROADCAST_ID = 0xFFFFFF; - - private byte mSourceId; - private @LeAudioBroadcastSourceAddressType int mSourceAddressType; - private BluetoothDevice mSourceDevice; - private byte mSourceAdvSid; - private int mBroadcastId; - private @LeAudioBroadcastSinkPaSyncState int mPaSyncState; - private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus; - private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState; - private byte[] mBadBroadcastCode; - private byte mNumSubGroups; - private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>(); - private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>(); - - private String mBroadcastCode; - private static final int BIS_NO_PREF = 0xFFFFFFFF; - private static final int BROADCAST_CODE_SIZE = 16; - - /** - * Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the - * given Source Id. - * - * <p>This is mainly used to represent the Empty Broadcast Source entries - * - * @param sourceId Source Id for this Broadcast Source info object - * @hide - */ - public BluetoothLeBroadcastSourceInfo(byte sourceId) { - mSourceId = sourceId; - mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID; - mSourceDevice = null; - mSourceAdvSid = (byte) 0x00; - mBroadcastId = INVALID_BROADCAST_ID; - mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID; - mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID; - mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID; - mBadBroadcastCode = null; - mNumSubGroups = 0; - mBroadcastCode = null; - } - - /*package*/ BluetoothLeBroadcastSourceInfo( - byte sourceId, - @LeAudioBroadcastSourceAddressType int addressType, - @NonNull BluetoothDevice device, - byte advSid, - int broadcastId, - @LeAudioBroadcastSinkPaSyncState int paSyncstate, - @LeAudioBroadcastSinkEncryptionState int encryptionStatus, - @LeAudioBroadcastSinkAudioSyncState int audioSyncstate, - @Nullable byte[] badCode, - byte numSubGroups, - @NonNull Map<Integer, Integer> bisSyncState, - @Nullable Map<Integer, byte[]> subgroupMetadata, - @NonNull String broadcastCode) { - mSourceId = sourceId; - mSourceAddressType = addressType; - mSourceDevice = device; - mSourceAdvSid = advSid; - mBroadcastId = broadcastId; - mPaSyncState = paSyncstate; - mEncryptionStatus = encryptionStatus; - mAudioSyncState = audioSyncstate; - - if (badCode != null && badCode.length != 0) { - mBadBroadcastCode = new byte[badCode.length]; - System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); - } - mNumSubGroups = numSubGroups; - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata); - mBroadcastCode = broadcastCode; - } - - @Override - public boolean equals(Object o) { - if (o instanceof BluetoothLeBroadcastSourceInfo) { - BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o; - return (other.mSourceId == mSourceId - && other.mSourceAddressType == mSourceAddressType - && other.mSourceDevice == mSourceDevice - && other.mSourceAdvSid == mSourceAdvSid - && other.mBroadcastId == mBroadcastId - && other.mPaSyncState == mPaSyncState - && other.mEncryptionStatus == mEncryptionStatus - && other.mAudioSyncState == mAudioSyncState - && Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode) - && other.mNumSubGroups == mNumSubGroups - && mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState) - && mSubgroupMetadata.equals(other.mSubgroupMetadata) - && other.mBroadcastCode == mBroadcastCode); - } - return false; - } - - /** - * Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty. - * - * @hide - */ - public boolean isEmpty() { - boolean ret = false; - if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - && mSourceDevice == null - && mSourceAdvSid == (byte) 0 - && mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID - && mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID - && mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID - && mBadBroadcastCode == null - && mNumSubGroups == 0 - && mSubgroupBisSyncState.size() == 0 - && mSubgroupMetadata.size() == 0 - && mBroadcastCode == null) { - ret = true; - } - return ret; - } - - /** - * Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance. - * - * @hide - */ - public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) { - boolean ret = false; - if (srcInfo == null) { - ret = false; - } else { - if (mSourceDevice == null) { - if (mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType()) { - ret = true; - } - } else { - if (mSourceDevice.equals(srcInfo.getSourceDevice()) - && mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType() - && mBroadcastId == srcInfo.getBroadcastId()) { - ret = true; - } - } - } - return ret; - } - - @Override - public int hashCode() { - return Objects.hash( - mSourceId, - mSourceAddressType, - mSourceDevice, - mSourceAdvSid, - mBroadcastId, - mPaSyncState, - mEncryptionStatus, - mAudioSyncState, - mBadBroadcastCode, - mNumSubGroups, - mSubgroupBisSyncState, - mSubgroupMetadata, - mBroadcastCode); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public String toString() { - return "{BluetoothLeBroadcastSourceInfo : mSourceId" - + mSourceId - + " addressType: " - + mSourceAddressType - + " sourceDevice: " - + mSourceDevice - + " mSourceAdvSid:" - + mSourceAdvSid - + " mBroadcastId:" - + mBroadcastId - + " mPaSyncState:" - + mPaSyncState - + " mEncryptionStatus:" - + mEncryptionStatus - + " mAudioSyncState:" - + mAudioSyncState - + " mBadBroadcastCode:" - + mBadBroadcastCode - + " mNumSubGroups:" - + mNumSubGroups - + " mSubgroupBisSyncState:" - + mSubgroupBisSyncState - + " mSubgroupMetadata:" - + mSubgroupMetadata - + " mBroadcastCode:" - + mBroadcastCode - + "}"; - } - - /** - * Get the Source Id - * - * @return byte representing the Source Id, {@link - * #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid - * @hide - */ - public byte getSourceId() { - return mSourceId; - } - - /** - * Set the Source Id - * - * @param sourceId source Id - * @hide - */ - public void setSourceId(byte sourceId) { - mSourceId = sourceId; - } - - /** - * Set the Broadcast Source device - * - * @param sourceDevice the Broadcast Source BluetoothDevice - * @hide - */ - public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) { - mSourceDevice = sourceDevice; - } - - /** - * Get the Broadcast Source BluetoothDevice - * - * @return Broadcast Source BluetoothDevice - * @hide - */ - public @NonNull BluetoothDevice getSourceDevice() { - return mSourceDevice; - } - - /** - * Set the address type of the Broadcast Source advertisements - * - * @hide - */ - public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) { - mSourceAddressType = addressType; - } - - /** - * Get the address type used by advertisements from the Broadcast Source. - * BluetoothLeBroadcastSourceInfo Object - * - * @hide - */ - @LeAudioBroadcastSourceAddressType - public int getAdvAddressType() { - return mSourceAddressType; - } - - /** - * Set the advertising SID of the Broadcast Source advertisement. - * - * @param advSid advertising SID of the Broadcast Source - * @hide - */ - public void setAdvertisingSid(byte advSid) { - mSourceAdvSid = advSid; - } - - /** - * Get the advertising SID of the Broadcast Source advertisement. - * - * @return advertising SID of the Broadcast Source - * @hide - */ - public byte getAdvertisingSid() { - return mSourceAdvSid; - } - - /** - * Get the Broadcast ID of the Broadcast Source. - * - * @return broadcast ID - * @hide - */ - public int getBroadcastId() { - return mBroadcastId; - } - - /** - * Set the Periodic Advertising (PA) Sync State. - * - * @hide - */ - /*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) { - mPaSyncState = paSyncState; - } - - /** - * Get the Periodic Advertising (PA) Sync State - * - * @hide - */ - public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() { - return mPaSyncState; - } - - /** - * Set the audio sync state - * - * @hide - */ - /*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) { - mAudioSyncState = audioSyncState; - } - - /** - * Get the audio sync state - * - * @hide - */ - public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() { - return mAudioSyncState; - } - - /** - * Set the encryption status - * - * @hide - */ - /*package*/ void setEncryptionStatus( - @LeAudioBroadcastSinkEncryptionState int encryptionStatus) { - mEncryptionStatus = encryptionStatus; - } - - /** - * Get the encryption status - * - * @hide - */ - public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() { - return mEncryptionStatus; - } - - /** - * Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio - * Stream and failed. - * - * <p>This code is valid only if {@link #getEncryptionStatus} returns {@link - * #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * - * @return byte array containing bad broadcast value, null if the current encryption status is - * not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * @hide - */ - public @Nullable byte[] getBadBroadcastCode() { - return mBadBroadcastCode; - } - - /** - * Get the number of subgroups. - * - * @return number of subgroups - * @hide - */ - public byte getNumberOfSubGroups() { - return mNumSubGroups; - } - - public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() { - return mSubgroupBisSyncState; - } - - public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) { - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - } - - /*package*/ void setBroadcastCode(@NonNull String broadcastCode) { - mBroadcastCode = broadcastCode; - } - - /** - * Get the broadcast code - * - * @return - * @hide - */ - public @NonNull String getBroadcastCode() { - return mBroadcastCode; - } - - /** - * Set the broadcast ID - * - * @param broadcastId broadcast ID of the Broadcast Source - * @hide - */ - public void setBroadcastId(int broadcastId) { - mBroadcastId = broadcastId; - } - - private void writeSubgroupBisSyncStateToParcel( - @NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - dest.writeInt(subgroupBisSyncState.size()); - for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) { - dest.writeInt(entry.getKey()); - dest.writeInt(entry.getValue()); - } - } - - private static void readSubgroupBisSyncStateFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer value = in.readInt(); - subgroupBisSyncState.put(key, value); - } - } - - private void writeSubgroupMetadataToParcel( - @NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) { - if (subgroupMetadata == null) { - dest.writeInt(0); - return; - } - - dest.writeInt(subgroupMetadata.size()); - for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) { - dest.writeInt(entry.getKey()); - byte[] metadata = entry.getValue(); - if (metadata != null) { - dest.writeInt(metadata.length); - dest.writeByteArray(metadata); - } - } - } - - private static void readSubgroupMetadataFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer metaDataLen = in.readInt(); - byte[] metadata = null; - if (metaDataLen != 0) { - metadata = new byte[metaDataLen]; - in.readByteArray(metadata); - } - subgroupMetadata.put(key, metadata); - } - } - - public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR = - new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() { - public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel( - @NonNull Parcel in) { - final byte sourceId = in.readByte(); - final int sourceAddressType = in.readInt(); - final BluetoothDevice sourceDevice = - in.readTypedObject(BluetoothDevice.CREATOR); - final byte sourceAdvSid = in.readByte(); - final int broadcastId = in.readInt(); - final int paSyncState = in.readInt(); - final int audioSyncState = in.readInt(); - final int encryptionStatus = in.readInt(); - final int badBroadcastLen = in.readInt(); - byte[] badBroadcastCode = null; - - if (badBroadcastLen > 0) { - badBroadcastCode = new byte[badBroadcastLen]; - in.readByteArray(badBroadcastCode); - } - final byte numSubGroups = in.readByte(); - final String broadcastCode = in.readString(); - Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>(); - readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState); - Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>(); - readSubgroupMetadataFromParcel(in, subgroupMetadata); - - BluetoothLeBroadcastSourceInfo srcInfo = - new BluetoothLeBroadcastSourceInfo( - sourceId, - sourceAddressType, - sourceDevice, - sourceAdvSid, - broadcastId, - paSyncState, - encryptionStatus, - audioSyncState, - badBroadcastCode, - numSubGroups, - subgroupBisSyncState, - subgroupMetadata, - broadcastCode); - return srcInfo; - } - - public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) { - return new BluetoothLeBroadcastSourceInfo[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeByte(mSourceId); - out.writeInt(mSourceAddressType); - out.writeTypedObject(mSourceDevice, 0); - out.writeByte(mSourceAdvSid); - out.writeInt(mBroadcastId); - out.writeInt(mPaSyncState); - out.writeInt(mAudioSyncState); - out.writeInt(mEncryptionStatus); - - if (mBadBroadcastCode != null) { - out.writeInt(mBadBroadcastCode.length); - out.writeByteArray(mBadBroadcastCode); - } else { - // zero indicates that there is no "bad broadcast code" - out.writeInt(0); - } - out.writeByte(mNumSubGroups); - out.writeString(mBroadcastCode); - writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState); - writeSubgroupMetadataToParcel(out, mSubgroupMetadata); - } - - private static void log(@NonNull String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} -; diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java b/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java new file mode 100644 index 0000000000..273ac43c37 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java @@ -0,0 +1,312 @@ +/* + * 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.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class contains the subgroup level information as defined in the BASE structure of Basic + * Audio profile. + * + * @hide + */ +@SystemApi +public final class BluetoothLeBroadcastSubgroup implements Parcelable { + private final long mCodecId; + private final BluetoothLeAudioCodecConfigMetadata mCodecSpecificConfig; + private final BluetoothLeAudioContentMetadata mContentMetadata; + private final boolean mNoChannelPreference; + private final List<BluetoothLeBroadcastChannel> mChannels; + + private BluetoothLeBroadcastSubgroup(long codecId, + BluetoothLeAudioCodecConfigMetadata codecSpecificConfig, + BluetoothLeAudioContentMetadata contentMetadata, boolean noChannelPreference, + List<BluetoothLeBroadcastChannel> channels) { + mCodecId = codecId; + mCodecSpecificConfig = codecSpecificConfig; + mContentMetadata = contentMetadata; + mNoChannelPreference = noChannelPreference; + mChannels = channels; + } + + /** + * Get the codec ID field as defined by the Basic Audio Profile. + * + * The codec ID field has 5 octets, with + * - Octet 0: Coding_Format as defined in Bluetooth Assigned Numbers + * - Octet 1-2: Company ID as defined in Bluetooth Assigned Numbers + * Shall be 0x0000 if octet 0 != 0xFF + * - Octet 3-4: Vendor-specific codec ID + * Shall be 0x0000 if octet 0 != 0xFF + * + * @return 5-byte codec ID field in Java long format + * @hide + */ + @SystemApi + public long getCodecId() { + return mCodecId; + } + + /** + * Get codec specific config metadata for this subgroup. + * + * @return codec specific config metadata for this subgroup + * @hide + */ + @SystemApi + @NonNull + public BluetoothLeAudioCodecConfigMetadata getCodecSpecificConfig() { + return mCodecSpecificConfig; + } + + /** + * Get content metadata for this Broadcast Source subgroup. + * + * @return content metadata for this Broadcast Source subgroup + * @hide + */ + @SystemApi + public @NonNull BluetoothLeAudioContentMetadata getContentMetadata() { + return mContentMetadata; + } + + /** + * Indicate if Broadcast Sink should have no Broadcast Channel (BIS) preference. + * + * Only used by Broadcast Assistant and Sink. Ignored by Broadcast Source + * + * @return true if Broadcast Sink should have no Broadcast Channel (BIS) preference + * @hide + */ + @SystemApi + public boolean isNoChannelPreference() { + return mNoChannelPreference; + } + + /** + * Get list of Broadcast Channels included in this Broadcast subgroup. + * + * Each Broadcast Channel represents a Broadcast Isochronous Stream (BIS) + * + * A Broadcast subgroup should contain at least 1 Broadcast Channel + * + * @return list of Broadcast Channels included in this Broadcast subgroup + * @hide + */ + @SystemApi + public @NonNull List<BluetoothLeBroadcastChannel> getChannels() { + return mChannels; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public int describeContents() { + return 0; + } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mCodecId); + out.writeTypedObject(mCodecSpecificConfig, 0); + out.writeTypedObject(mContentMetadata, 0); + out.writeBoolean(mNoChannelPreference); + out.writeTypedList(mChannels); + } + + /** + * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastSubgroup} from parcel. + * @hide + */ + @SystemApi + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSubgroup> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastSubgroup>() { + public @NonNull BluetoothLeBroadcastSubgroup createFromParcel(@NonNull Parcel in) { + Builder builder = new Builder(); + builder.setCodecId(in.readLong()); + builder.setCodecSpecificConfig(in.readTypedObject( + BluetoothLeAudioCodecConfigMetadata.CREATOR)); + builder.setNoChannelPreference(in.readBoolean()); + List<BluetoothLeBroadcastChannel> channels = new ArrayList<>(); + in.readTypedList(channels, BluetoothLeBroadcastChannel.CREATOR); + for (BluetoothLeBroadcastChannel channel : channels) { + builder.addChannel(channel); + } + return builder.build(); + } + + public @NonNull BluetoothLeBroadcastSubgroup[] newArray(int size) { + return new BluetoothLeBroadcastSubgroup[size]; + } + }; + + private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; + + /** + * Builder for {@link BluetoothLeBroadcastSubgroup}. + * @hide + */ + @SystemApi + public static final class Builder { + private long mCodecId = UNKNOWN_VALUE_PLACEHOLDER; + private BluetoothLeAudioCodecConfigMetadata mCodecSpecificConfig = null; + private BluetoothLeAudioContentMetadata mContentMetadata = null; + private boolean mNoChannelPreference = false; + private List<BluetoothLeBroadcastChannel> mChannels = new ArrayList<>(); + + /** + * Create an empty constructor. + * @hide + */ + @SystemApi + public Builder() {} + + /** + * Create a builder with copies of information from original object. + * + * @param original original object + * @hide + */ + @SystemApi + public Builder(@NonNull BluetoothLeBroadcastSubgroup original) { + mCodecId = original.getCodecId(); + mCodecSpecificConfig = original.getCodecSpecificConfig(); + mContentMetadata = original.getContentMetadata(); + mNoChannelPreference = original.isNoChannelPreference(); + mChannels = original.getChannels(); + } + + + /** + * Set the codec ID field as defined by the Basic Audio Profile. + * + * The codec ID field has 5 octets, with + * - Octet 0: Coding_Format as defined in Bluetooth Assigned Numbers + * - Octet 1-2: Company ID as defined in Bluetooth Assigned Numbers + * Shall be 0x0000 if octet 0 != 0xFF + * - Octet 3-4: Vendor-specific codec ID + * Shall be 0x0000 if octet 0 != 0xFF + * + * @param codecId 5-byte codec ID field in Java long format + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setCodecId(long codecId) { + mCodecId = codecId; + return this; + } + + /** + * Set codec specific config metadata for this subgroup. + * + * @param codecSpecificConfig codec specific config metadata for this subgroup + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setCodecSpecificConfig( + @NonNull BluetoothLeAudioCodecConfigMetadata codecSpecificConfig) { + mCodecSpecificConfig = codecSpecificConfig; + return this; + } + + /** + * Set content metadata for this Broadcast Source subgroup. + * + * @param contentMetadata content metadata for this Broadcast Source subgroup + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setContentMetadata( + @NonNull BluetoothLeAudioContentMetadata contentMetadata) { + mContentMetadata = contentMetadata; + return this; + } + + /** + * Set if Broadcast Sink should have no Broadcast Channel (BIS) preference. + * + * Only used by Broadcast Assistant and Sink. Ignored by Broadcast Source + * + * @param isNoChannelPreference true if Broadcast Sink should have no Broadcast Channel + * (BIS) preference + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder setNoChannelPreference(boolean isNoChannelPreference) { + mNoChannelPreference = isNoChannelPreference; + return this; + } + + /** + * Add a Broadcast Channel to this Broadcast subgroup. + * + * Each Broadcast Channel represents a Broadcast Isochronous Stream (BIS) + * + * A Broadcast subgroup should contain at least 1 Broadcast Channel + * + * @param channel a Broadcast Channel to be added to this Broadcast subgroup + * @hide + */ + @SystemApi + public @NonNull Builder addChannel(@NonNull BluetoothLeBroadcastChannel channel) { + mChannels.add(channel); + return this; + } + + /** + * Clear channel list so that one can reset the builder after create it from an existing + * object. + * + * @return this builder + * @hide + */ + @SystemApi + public @NonNull Builder clearChannel() { + mChannels.clear(); + return this; + } + + /** + * Build {@link BluetoothLeBroadcastSubgroup}. + * + * @return constructed {@link BluetoothLeBroadcastSubgroup} + * @throws IllegalArgumentException if the object cannot be built + * @hide + */ + @SystemApi + public @NonNull BluetoothLeBroadcastSubgroup build() { + return new BluetoothLeBroadcastSubgroup(mCodecId, mCodecSpecificConfig, + mContentMetadata, mNoChannelPreference, mChannels); + } + } +} diff --git a/framework/java/android/bluetooth/BluetoothProfile.java b/framework/java/android/bluetooth/BluetoothProfile.java index 1fa2bcfd44..c29a671488 100644 --- a/framework/java/android/bluetooth/BluetoothProfile.java +++ b/framework/java/android/bluetooth/BluetoothProfile.java @@ -237,6 +237,7 @@ public interface BluetoothProfile { * * @hide */ + @SystemApi int LE_AUDIO_BROADCAST = 26; /** @@ -253,12 +254,20 @@ public interface BluetoothProfile { int HAP_CLIENT = 28; /** + * LE Audio Broadcast Assistant + * + * @hide + */ + @SystemApi + int LE_AUDIO_BROADCAST_ASSISTANT = 29; + + /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 28; + int MAX_PROFILE_ID = 29; /** * Default priority for devices that we try to auto-connect to and diff --git a/framework/java/android/bluetooth/BluetoothStatusCodes.java b/framework/java/android/bluetooth/BluetoothStatusCodes.java index 0425220c8d..5f90d7ed48 100644 --- a/framework/java/android/bluetooth/BluetoothStatusCodes.java +++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java @@ -104,6 +104,94 @@ public final class BluetoothStatusCodes { public static final int ERROR_TIMEOUT = 15; /** + * Indicates that some local application caused the event. + * @hide + */ + @SystemApi + public static final int REASON_LOCAL_APP_REQUEST = 16; + + /** + * Indicate that this change was initiated by the Bluetooth implementation on this device + * @hide + */ + @SystemApi + public static final int REASON_LOCAL_STACK_REQUEST = 17; + + /** + * Indicate that this change was initiated by the remote device. + * @hide + */ + @SystemApi + public static final int REASON_REMOTE_REQUEST = 18; + + /** + * Indicates that the local system policy caused the change, such as privacy policy, power + * management policy, permission changes, and more. + * @hide + */ + @SystemApi + public static final int REASON_SYSTEM_POLICY = 19; + + /** + * Indicates that an underlying hardware incurred some error maybe try again later or toggle + * the hardware state. + * @hide + */ + @SystemApi + public static final int ERROR_HARDWARE_GENERIC = 20; + + /** + * Indicates that the operation failed due to bad API input parameter that is not covered + * by other more detailed error code + * @hide + */ + @SystemApi + public static final int ERROR_BAD_PARAMETERS = 21; + + /** + * Indicate that there is not enough local resource to perform the requested operation + * @hide + */ + @SystemApi + public static final int ERROR_LOCAL_NOT_ENOUGH_RESOURCES = 22; + + /** + * Indicate that a remote device does not have enough resource to perform the requested + * operation + * @hide + */ + @SystemApi + public static final int ERROR_REMOTE_NOT_ENOUGH_RESOURCES = 23; + + /** + * Indicates that the remote rejected this operation for reasons not covered above + * @hide + */ + @SystemApi + public static final int ERROR_REMOTE_OPERATION_REJECTED = 24; + + /** + * Indicates that there is an underlying link error between the local and remote devices. + * + * Maybe try again later or disconnect and retry. + * @hide + */ + @SystemApi + public static final int ERROR_REMOTE_LINK_ERROR = 25; + + /** + * A generic error code to indicate that the system is already in a target state that an API + * tries to request. + * + * For example, this error code will be delivered if someone tries to stop scanning when + * scan has already stopped, or start scanning when scan has already started. + * + * @hide + */ + @SystemApi + public static final int ERROR_ALREADY_IN_TARGET_STATE = 26; + + /** * A GATT writeCharacteristic request is not permitted on the remote device. */ public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; @@ -249,65 +337,60 @@ public final class BluetoothStatusCodes { */ public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109; + // LE audio related return codes reserved from 1200 to 1300 + /** - * Indicates that setting the LE Audio Broadcast mode failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * + * Indicates that the broadcast ID cannot be found among existing Broadcast Sources. * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110; + @SystemApi + public static final int ERROR_LE_BROADCAST_INVALID_BROADCAST_ID = 1200; /** - * Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * + * Indicates that encryption code entered does not meet the specification requirement * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111; + @SystemApi + public static final int ERROR_LE_BROADCAST_INVALID_CODE = 1201; /** - * Indicates that connecting to a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * + * Indicates that the source ID cannot be found in the given Broadcast sink device * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112; + @SystemApi + public static final int ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID = 1202; /** - * Indicates that disconnecting from a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. + * Indicates that the same Broadcast Source is already added to the Broadcast Sink * + * Broadcast Source is identified by their advertising SID and broadcast ID * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113; + @SystemApi + public static final int ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION = 1203; + /** - * Indicates that enabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * + * Indicates that the program info in a {@link BluetoothLeAudioContentMetadata} is not valid * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114; + @SystemApi + public static final int ERROR_LE_CONTENT_METADATA_INVALID_PROGRAM_INFO = 1204; /** - * Indicates that disabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * + * Indicates that the language code in a {@link BluetoothLeAudioContentMetadata} is not valid * @hide */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115; + @SystemApi + public static final int ERROR_LE_CONTENT_METADATA_INVALID_LANGUAGE = 1205; + + /** + * Indicates that operation failed due to other {@link BluetoothLeAudioContentMetadata} related + * issues not covered by other reason codes. + * @hide + */ + @SystemApi + public static final int ERROR_LE_CONTENT_METADATA_INVALID_OTHER = 1206; /** * Indicates that the RFCOMM listener could not be started due to the requested UUID already @@ -361,7 +444,7 @@ public final class BluetoothStatusCodes { public static final int RFCOMM_LISTENER_NO_SOCKET_AVAILABLE = 2005; /** - * Indicates that an unknown error has occurred has occurred. + * Indicates that an unknown error has occurred. */ public static final int ERROR_UNKNOWN = Integer.MAX_VALUE; } |