blob: 81fdb121a2e044675e3231cb5a4018132ce3ef9b [file] [log] [blame]
/*
* 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;
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.
*
* <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.
*
* @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<Long> bisSyncState,
@NonNull List<BluetoothLeAudioContentMetadata> 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<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
@NonNull
public static final Creator<BluetoothLeBroadcastReceiveState> 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<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];
}
};
}