/* * 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.Arrays; import java.util.List; import java.util.Objects; /** * 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. * *

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). * *

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 mBisSyncState; private final List mSubgroupMetadata; private static String paSyncStateToString(int paSyncState) { switch (paSyncState) { case 0x00: return "Not synchronized to PA: [" + Integer.toString(paSyncState) + "]"; case 0x01: return "SyncInfo Request: [" + Integer.toString(paSyncState) + "]"; case 0x02: return "Synchronized to PA: [" + Integer.toString(paSyncState) + "]"; case 0x03: return "Failed to synchronize to PA: [" + Integer.toString(paSyncState) + "]"; case 0x04: return "No PAST: [" + Integer.toString(paSyncState) + "]"; default: return "RFU: [" + Integer.toString(paSyncState) + "]"; } } private static String bigEncryptionStateToString(int bigEncryptionState) { switch (bigEncryptionState) { case 0x00: return "Not encrypted: [" + Integer.toString(bigEncryptionState) + "]"; case 0x01: return "Broadcast_Code required: [" + Integer.toString(bigEncryptionState) + "]"; case 0x02: return "Decrypting: [" + Integer.toString(bigEncryptionState) + "]"; case 0x03: return "Bad_Code (incorrect encryption key): [" + Integer.toString(bigEncryptionState) + "]"; default: return "RFU: [" + Integer.toString(bigEncryptionState) + "]"; } } private static String bisSyncStateToString(Long bisSyncState, int bisSyncStateIndex) { if (bisSyncState == 0) { return "Not synchronized to BIS_index[" + Integer.toString(bisSyncStateIndex) + "]: [" + String.valueOf(bisSyncState) + "]"; } else if (bisSyncState > 0 && bisSyncState < 0xFFFFFFFF) { return "Synchronized to BIS_index[" + Integer.toString(bisSyncStateIndex) + "]: [" + String.valueOf(bisSyncState) + "]"; } else if (bisSyncState == 0xFFFFFFFF) { return "Failed to sync to BIG: [" + String.valueOf(bisSyncState) + "]"; } else { return "[" + String.valueOf(bisSyncState) + "]"; } } /** * Constructor to create a read-only {@link BluetoothLeBroadcastReceiveState} instance. * * @throws NullPointerException if sourceDevice, bisSyncState, or subgroupMetadata is null * @throws IllegalArgumentException if sourceID is not [0, 0xFF] or if sourceAddressType is * invalid or if bisSyncState.size() != numSubgroups or if subgroupMetadata.size() != * numSubgroups or if paSyncState or bigEncryptionState is not recognized bye IntDef * @hide */ public BluetoothLeBroadcastReceiveState( @IntRange(from = 0x00, to = 0xFF) int sourceId, @BluetoothDevice.AddressType int sourceAddressType, @NonNull BluetoothDevice sourceDevice, int sourceAdvertisingSid, int broadcastId, @PaSyncState int paSyncState, @BigEncryptionState int bigEncryptionState, byte[] badCode, @IntRange(from = 0x00) int numSubgroups, @NonNull List bisSyncState, @NonNull List subgroupMetadata) { if (sourceId < 0x00 || sourceId > 0xFF) { throw new IllegalArgumentException( "sourceId " + sourceId + " does not fall between 0x00 and 0xFF"); } Objects.requireNonNull(sourceDevice, "sourceDevice cannot be null"); if (sourceAddressType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) { throw new IllegalArgumentException("sourceAddressType cannot be ADDRESS_TYPE_UNKNOWN"); } if (sourceAddressType != BluetoothDevice.ADDRESS_TYPE_RANDOM && sourceAddressType != BluetoothDevice.ADDRESS_TYPE_PUBLIC) { throw new IllegalArgumentException( "sourceAddressType " + sourceAddressType + " is invalid"); } Objects.requireNonNull(bisSyncState, "bisSyncState cannot be null"); if (bisSyncState.size() != numSubgroups) { throw new IllegalArgumentException( "bisSyncState.size() " + bisSyncState.size() + " must be equal to numSubgroups " + numSubgroups); } Objects.requireNonNull(subgroupMetadata, "subgroupMetadata cannot be null"); if (subgroupMetadata.size() != numSubgroups) { throw new IllegalArgumentException( "subgroupMetadata.size() " + subgroupMetadata.size() + " must be equal to numSubgroups " + numSubgroups); } if (paSyncState != PA_SYNC_STATE_IDLE && paSyncState != PA_SYNC_STATE_SYNCINFO_REQUEST && paSyncState != PA_SYNC_STATE_SYNCHRONIZED && paSyncState != PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE && paSyncState != PA_SYNC_STATE_NO_PAST && paSyncState != PA_SYNC_STATE_INVALID) { throw new IllegalArgumentException("unrecognized paSyncState " + paSyncState); } if (bigEncryptionState != BIG_ENCRYPTION_STATE_NOT_ENCRYPTED && bigEncryptionState != BIG_ENCRYPTION_STATE_CODE_REQUIRED && bigEncryptionState != BIG_ENCRYPTION_STATE_DECRYPTING && bigEncryptionState != BIG_ENCRYPTION_STATE_BAD_CODE && bigEncryptionState != BIG_ENCRYPTION_STATE_INVALID) { throw new IllegalArgumentException( "unrecognized bigEncryptionState " + bigEncryptionState); } if (badCode != null && badCode.length != 16) { throw new IllegalArgumentException( "badCode must be 16 bytes long of null, but is " + badCode.length + " + bytes long"); } 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 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 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); } /** * {@inheritDoc} * * @hide */ @Override public String toString() { String receiveState = ("Receiver state: " + "\n Source ID:" + mSourceId + "\n Source Address Type:" + (int) mSourceAddressType + "\n Source Address:" + mSourceDevice.toString() + "\n Source Adv SID:" + mSourceAdvertisingSid + "\n Broadcast ID:" + mBroadcastId + "\n PA Sync State:" + paSyncStateToString(mPaSyncState) + "\n BIG Encryption Status:" + bigEncryptionStateToString(mBigEncryptionState) + "\n Bad Broadcast Code:" + Arrays.toString(mBadCode) + "\n Number Of Subgroups:" + mNumSubgroups); for (int i = 0; i < mNumSubgroups; i++) { receiveState += ("\n Subgroup index:" + i + "\n BIS Sync State:" + bisSyncStateToString(mBisSyncState.get(i), i)) + "\n Metadata:" + "\n ProgramInfo:" + mSubgroupMetadata.get(i).getProgramInfo() + "\n Language:" + mSubgroupMetadata.get(i).getLanguage() + "\n RawData:" + Arrays.toString(mSubgroupMetadata.get(i).getRawMetadata()); } return receiveState; } /** * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastReceiveState} from parcel. * * @hide */ @SystemApi @NonNull public static final Creator CREATOR = new Creator<>() { 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 bisSyncState = in.readArrayList(Long.class.getClassLoader(), Long.class); final List 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]; } }; }