/*
 * 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.FlaggedApi;
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 com.android.bluetooth.flags.Flags;

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;

/**
 * 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 boolean mIsPublicBroadcast;
    private final String mBroadcastName;
    private final byte[] mBroadcastCode;
    private final BluetoothLeAudioContentMetadata mPublicBroadcastMetadata;
    private final @AudioConfigQuality int mAudioConfigQuality;
    private final int mRssi;

    /**
     * Audio configuration quality for this Broadcast Group. This quality bitmap is used for
     * presenting the audio stream quality for this BIG, either public broadcast or non-public
     * broadcast Bit0 indicates at least one broadcast Audio Stream configuration is standard
     * quality Bit1 indicates at least one broadcast Audio Stream configuration is high quality
     *
     * @hide
     */
    @IntDef(
            flag = true,
            prefix = "AUDIO_CONFIG_QUALITY_",
            value = {
                AUDIO_CONFIG_QUALITY_NONE,
                AUDIO_CONFIG_QUALITY_STANDARD,
                AUDIO_CONFIG_QUALITY_HIGH,
            })
    @Retention(RetentionPolicy.SOURCE)
    public @interface AudioConfigQuality {}

    /**
     * Audio config quality is none, default value used for audio config quality.
     *
     * @hide
     */
    @SystemApi public static final int AUDIO_CONFIG_QUALITY_NONE = 0;

    /**
     * Audio config quality is standard. This indicates the BIG shall include at least one broadcast
     * Audio Stream configuration defined as Mandatory for a Broadcast Sink in Basic Audio Profile,
     * Version 1 or later, table 6.4
     *
     * @hide
     */
    @SystemApi public static final int AUDIO_CONFIG_QUALITY_STANDARD = 0x1 << 0;

    /**
     * Audio config quality is standard. This indicates the BIG shall include at least one broadcast
     * Audio Stream configuration setting listed in Public Broadcast Profile, Version 1 or later,
     * table 4.2
     *
     * @hide
     */
    @SystemApi public static final int AUDIO_CONFIG_QUALITY_HIGH = 0x1 << 1;

    // 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,
            boolean isPublicBroadcast,
            String broadcastName,
            byte[] broadcastCode,
            int presentationDelay,
            @AudioConfigQuality int audioConfigQuality,
            int rssi,
            BluetoothLeAudioContentMetadata publicBroadcastMetadata,
            List<BluetoothLeBroadcastSubgroup> subgroups) {
        mSourceAddressType = sourceAddressType;
        mSourceDevice = sourceDevice;
        mSourceAdvertisingSid = sourceAdvertisingSid;
        mBroadcastId = broadcastId;
        mPaSyncInterval = paSyncInterval;
        mIsEncrypted = isEncrypted;
        mIsPublicBroadcast = isPublicBroadcast;
        mBroadcastName = broadcastName;
        mBroadcastCode = broadcastCode;
        mPresentationDelayMicros = presentationDelay;
        mAudioConfigQuality = audioConfigQuality;
        mRssi = rssi;
        mPublicBroadcastMetadata = publicBroadcastMetadata;
        mSubgroups = subgroups;
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (!(o instanceof BluetoothLeBroadcastMetadata)) {
            return false;
        }
        final BluetoothLeBroadcastMetadata other = (BluetoothLeBroadcastMetadata) o;
        return mSourceAddressType == other.getSourceAddressType()
                && mSourceDevice.equals(other.getSourceDevice())
                && mSourceAdvertisingSid == other.getSourceAdvertisingSid()
                && mBroadcastId == other.getBroadcastId()
                && mPaSyncInterval == other.getPaSyncInterval()
                && mIsEncrypted == other.isEncrypted()
                && mIsPublicBroadcast == other.isPublicBroadcast()
                && Objects.equals(mBroadcastName, other.getBroadcastName())
                && Arrays.equals(mBroadcastCode, other.getBroadcastCode())
                && mPresentationDelayMicros == other.getPresentationDelayMicros()
                && mAudioConfigQuality == other.getAudioConfigQuality()
                && mRssi == other.getRssi()
                && Objects.equals(mPublicBroadcastMetadata, other.getPublicBroadcastMetadata())
                && mSubgroups.equals(other.getSubgroups());
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                mSourceAddressType,
                mSourceDevice,
                mSourceAdvertisingSid,
                mBroadcastId,
                mPaSyncInterval,
                mIsEncrypted,
                mIsPublicBroadcast,
                mBroadcastName,
                Arrays.hashCode(mBroadcastCode),
                mPresentationDelayMicros,
                mAudioConfigQuality,
                mRssi,
                mPublicBroadcastMetadata,
                mSubgroups);
    }

    @Override
    public String toString() {
        return "BluetoothLeBroadcastMetadata{"
                + ("sourceAddressType=" + mSourceAddressType)
                + (", sourceDevice=" + mSourceDevice)
                + (", sourceAdvertisingSid=" + mSourceAdvertisingSid)
                + (", broadcastId=" + mBroadcastId)
                + (", paSyncInterval=" + mPaSyncInterval)
                + (", isEncrypted=" + mIsEncrypted)
                + (", isPublicBroadcast=" + mIsPublicBroadcast)
                + (", broadcastName=" + mBroadcastName)
                + (", broadcastCode=" + Arrays.toString(mBroadcastCode))
                + (", presentationDelayMicros=" + mPresentationDelayMicros)
                + (", audioConfigQuality=" + mAudioConfigQuality)
                + (", rssi=" + mRssi)
                + (", publicBroadcastMetadata=" + mPublicBroadcastMetadata)
                + (", subgroups=" + mSubgroups)
                + '}';
    }

    /**
     * Get the address type of the Broadcast Source.
     *
     * <p>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 @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;
    }

    /**
     * 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;
    }

    /**
     * Return {@code true} if this Broadcast Group is broadcasting Public Broadcast Announcement
     * otherwise return {@code false}.
     *
     * @hide
     */
    @SystemApi
    public boolean isPublicBroadcast() {
        return mIsPublicBroadcast;
    }

    /**
     * Get the broadcast name for this Broadcast Group as UTF-8 format.
     *
     * @return broadcast name or null for this Broadcast Group
     * @hide
     */
    @SystemApi
    public @Nullable String getBroadcastName() {
        return mBroadcastName;
    }

    /**
     * Get the Broadcast Code currently set for this Broadcast Source.
     *
     * <p>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, {@code 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.
     *
     * <p>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 broadcast audio config quality for this Broadcast Group.
     *
     * @return Broadcast audio config quality for this Broadcast Group
     * @hide
     */
    @SystemApi
    public @AudioConfigQuality int getAudioConfigQuality() {
        return mAudioConfigQuality;
    }

    /**
     * Indicated that rssi value is unknown.
     *
     * @hide
     */
    @FlaggedApi(Flags.FLAG_LEAUDIO_BROADCAST_MONITOR_SOURCE_SYNC_STATUS)
    @SystemApi
    public static final int RSSI_UNKNOWN = 0x7F;

    /**
     * Get the Received Signal Strength Indication (RSSI) value of this Broadcast Source.
     *
     * <p>The valid RSSI range is [-127, 126] and as defined in Volume 4, Part E, Section 7.7.65.13
     * of Bluetooth Core Specification, Version 5.3, value of 0x7F(127) means that the RSSI is not
     * available.
     *
     * @return the RSSI {@link #RSSI_UNKNOWN} if unknown
     * @hide
     */
    @FlaggedApi(Flags.FLAG_LEAUDIO_BROADCAST_MONITOR_SOURCE_SYNC_STATUS)
    @SystemApi
    public @IntRange(from = -127, to = 127) int getRssi() {
        return mRssi;
    }

    /**
     * Get public broadcast metadata for this Broadcast Group.
     *
     * @return public broadcast metadata for this Broadcast Group, {@code null} if no public
     *     metadata exists
     * @hide
     */
    @SystemApi
    public @Nullable BluetoothLeAudioContentMetadata getPublicBroadcastMetadata() {
        return mPublicBroadcastMetadata;
    }

    /**
     * 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);
        out.writeBoolean(mIsPublicBroadcast);
        out.writeString(mBroadcastName);
        out.writeInt(mAudioConfigQuality);
        out.writeTypedObject(mPublicBroadcastMetadata, 0);
        out.writeInt(mRssi);
    }

    /**
     * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastMetadata} from parcel.
     *
     * @hide
     */
    @SystemApi @NonNull
    public static final Creator<BluetoothLeBroadcastMetadata> CREATOR =
            new Creator<>() {
                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);
                    }
                    builder.setPublicBroadcast(in.readBoolean());
                    builder.setBroadcastName(in.readString());
                    builder.setAudioConfigQuality(in.readInt());
                    builder.setPublicBroadcastMetadata(
                            in.readTypedObject(BluetoothLeAudioContentMetadata.CREATOR));
                    builder.setRssi(in.readInt());
                    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 = PA_SYNC_INTERVAL_UNKNOWN;
        private boolean mIsEncrypted = false;
        private boolean mIsPublicBroadcast = false;
        private String mBroadcastName = null;
        private byte[] mBroadcastCode = null;
        private int mPresentationDelayMicros = UNKNOWN_VALUE_PLACEHOLDER;
        private @AudioConfigQuality int mAudioConfigQuality = AUDIO_CONFIG_QUALITY_NONE;
        private int mRssi = RSSI_UNKNOWN;
        private BluetoothLeAudioContentMetadata mPublicBroadcastMetadata = null;
        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();
            mIsPublicBroadcast = original.isPublicBroadcast();
            mBroadcastName = original.getBroadcastName();
            mBroadcastCode = original.getBroadcastCode();
            mPresentationDelayMicros = original.getPresentationDelayMicros();
            mAudioConfigQuality = original.getAudioConfigQuality();
            mRssi = original.getRssi();
            mPublicBroadcastMetadata = original.getPublicBroadcastMetadata();
            mSubgroups = original.getSubgroups();
        }

        /**
         * Set the address type and MAC address of the Broadcast Source.
         *
         * <p>Address type can be either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}, {@link
         * BluetoothDevice#ADDRESS_TYPE_RANDOM}
         *
         * <p>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
         * @throws IllegalArgumentException if sourceAddressType is invalid
         * @throws NullPointerException if sourceDevice is null
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setSourceDevice(
                @NonNull BluetoothDevice sourceDevice,
                @BluetoothDevice.AddressType int sourceAddressType) {
            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(sourceDevice, "sourceDevice cannot be null");
            mSourceAddressType = sourceAddressType;
            mSourceDevice = sourceDevice;
            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.
         *
         * <p>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 whether this Broadcast Group is broadcasting Public Broadcast Announcement.
         *
         * @param isPublicBroadcast whether this Broadcast Group is broadcasting Public Broadcast
         *     Announcement
         * @return this builder
         * @hide
         */
        @SystemApi
        public @NonNull Builder setPublicBroadcast(boolean isPublicBroadcast) {
            mIsPublicBroadcast = isPublicBroadcast;
            return this;
        }

        /**
         * Set broadcast name for this Broadcast Group.
         *
         * @param broadcastName Broadcast name for this Broadcast Group, {@code null} if no name
         *     provided
         * @return this builder
         * @hide
         */
        @SystemApi
        public @NonNull Builder setBroadcastName(@Nullable String broadcastName) {
            mBroadcastName = broadcastName;
            return this;
        }

        /**
         * Set the Broadcast Code currently set for this broadcast group.
         *
         * <p>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, {@code 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.
         *
         * <p>Presentation delay is defined in Section 7 of the Basic Audio Profile.
         *
         * @param presentationDelayMicros presentation delay of this Broadcast Source in
         *     microseconds
         * @throws IllegalArgumentException if presentationDelayMicros does not fall in [0,
         *     0xFFFFFF]
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setPresentationDelayMicros(
                @IntRange(from = 0, to = 0xFFFFFF) int presentationDelayMicros) {
            if (presentationDelayMicros < 0 || presentationDelayMicros >= 0xFFFFFF) {
                throw new IllegalArgumentException(
                        "presentationDelayMicros "
                                + presentationDelayMicros
                                + " does not fall in [0, 0xFFFFFF]");
            }
            mPresentationDelayMicros = presentationDelayMicros;
            return this;
        }

        /**
         * Set broadcast audio config quality for this Broadcast Group.
         *
         * @param audioConfigQuality broadcast audio config quality for this Broadcast Group
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setAudioConfigQuality(@AudioConfigQuality int audioConfigQuality) {
            mAudioConfigQuality = audioConfigQuality;
            return this;
        }

        /**
         * Set the Received Signal Strength Indication (RSSI) value for this Broadcast metadata.
         *
         * <p>The valid RSSI range is [-127, 126] and as defined in Volume 4, Part E, Section
         * 7.7.65.13 of Bluetooth Core Specification, Version 5.3, value of 0x7F(127) means that the
         * RSSI is not available.
         *
         * @param rssi the RSSI
         * @return this builder
         * @throws IllegalArgumentException if rssi is not in the range [-127, 127].
         * @hide
         */
        @FlaggedApi(Flags.FLAG_LEAUDIO_BROADCAST_MONITOR_SOURCE_SYNC_STATUS)
        @SystemApi
        @NonNull
        public Builder setRssi(@IntRange(from = -127, to = 127) int rssi) {
            if (rssi < -127 || rssi > 127) {
                throw new IllegalArgumentException("illegal rssi " + rssi);
            }
            mRssi = rssi;
            return this;
        }

        /**
         * Set public broadcast metadata for this Broadcast Group. PBS should include the
         * Program_Info length-type-value (LTV) structure metadata
         *
         * @param publicBroadcastMetadata public broadcast metadata for this Broadcast Group, {@code
         *     null} if no public meta data provided
         * @return this builder
         * @hide
         */
        @SystemApi
        @NonNull
        public Builder setPublicBroadcastMetadata(
                @Nullable BluetoothLeAudioContentMetadata publicBroadcastMetadata) {
            mPublicBroadcastMetadata = publicBroadcastMetadata;
            return this;
        }

        /**
         * Add a subgroup to this broadcast source.
         *
         * @param subgroup {@link BluetoothLeBroadcastSubgroup} that contains a subgroup's metadata
         * @throws NullPointerException if subgroup is null
         * @return this builder
         * @hide
         */
        @SystemApi
        public @NonNull Builder addSubgroup(@NonNull BluetoothLeBroadcastSubgroup subgroup) {
            Objects.requireNonNull(subgroup, "subgroup cannot be null");
            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
         * @throws NullPointerException if {@link NonNull} items are null
         * @hide
         */
        @SystemApi
        public @NonNull BluetoothLeBroadcastMetadata build() {
            if (mSourceAddressType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
                throw new IllegalArgumentException("SourceAddressTyp cannot be unknown");
            }
            if (mSourceAddressType != BluetoothDevice.ADDRESS_TYPE_RANDOM
                    && mSourceAddressType != BluetoothDevice.ADDRESS_TYPE_PUBLIC) {
                throw new IllegalArgumentException(
                        "sourceAddressType " + mSourceAddressType + " is invalid");
            }
            Objects.requireNonNull(mSourceDevice, "mSourceDevice cannot be null");
            if (mSubgroups.isEmpty()) {
                throw new IllegalArgumentException("Must contain at least one subgroup");
            }
            return new BluetoothLeBroadcastMetadata(
                    mSourceAddressType,
                    mSourceDevice,
                    mSourceAdvertisingSid,
                    mBroadcastId,
                    mPaSyncInterval,
                    mIsEncrypted,
                    mIsPublicBroadcast,
                    mBroadcastName,
                    mBroadcastCode,
                    mPresentationDelayMicros,
                    mAudioConfigQuality,
                    mRssi,
                    mPublicBroadcastMetadata,
                    mSubgroups);
        }
    }
}
