/*
 * Copyright (C) 2019 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.net.wifi;

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.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.net.MacAddress;
import android.net.wifi.util.Environment;
import android.net.wifi.util.HexEncoding;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;

import androidx.annotation.Keep;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;

import com.android.internal.util.Preconditions;
import com.android.modules.utils.build.SdkLevel;
import com.android.wifi.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Configuration for a soft access point (a.k.a. Soft AP, SAP, Hotspot).
 *
 * <p>This is input for the framework provided by a client app, i.e. it exposes knobs to instruct
 * the framework how it should configure a hotspot.
 *
 * <p>System apps can use this to configure a tethered hotspot or local-only hotspot.
 *
 * <p>Instances of this class are immutable.
 */
public final class SoftApConfiguration implements Parcelable {

    private static final String TAG = "SoftApConfiguration";

    @VisibleForTesting
    static final int PSK_MIN_LEN = 8;

    @VisibleForTesting
    static final int PSK_MAX_LEN = 63;

    /**
     * 2GHz band.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    public static final int BAND_2GHZ = 1 << 0;

    /**
     * 5GHz band.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    public static final int BAND_5GHZ = 1 << 1;

    /**
     * 6GHz band.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    public static final int BAND_6GHZ = 1 << 2;

    /**
     * 60GHz band.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    public static final int BAND_60GHZ = 1 << 3;

    /**
     * Device is allowed to choose the optimal band (2GHz, 5GHz, 6GHz) based on device capability,
     * operating country code and current radio conditions.
     * @hide
     *
     * @deprecated This is no longer supported. The value is fixed at
     * (BAND_2GHZ | BAND_5GHZ | BAND_6GHZ) even if a new band is supported in the future, for
     * instance {@code BAND_60GHZ}. The bands are a bit mask - use any combination of
     * {@code BAND_}, for instance {@code BAND_2GHZ | BAND_5GHZ}.
     */
    @SystemApi
    public static final int BAND_ANY = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ;

    /**
     * A default value used to configure shut down timeout setting to default value.
     * See {@link Builder#setShutdownTimeoutMillis(long)} or
     * {@link Builder#setBridgedModeOpportunisticShutdownTimeoutMillis(long)} for details.
     *
     * @hide
     */
    @SystemApi
    public static final long DEFAULT_TIMEOUT = -1;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, prefix = { "BAND_TYPE_" }, value = {
            BAND_2GHZ,
            BAND_5GHZ,
            BAND_6GHZ,
            BAND_60GHZ,
    })
    public @interface BandType {}

    /**
     * All of the supported band types.
     * @hide
     */
    public static int[] BAND_TYPES = {BAND_2GHZ, BAND_5GHZ, BAND_6GHZ, BAND_60GHZ};

    private static boolean isBandValid(@BandType int band) {
        int bandAny = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ | BAND_60GHZ;
        return ((band != 0) && ((band & ~bandAny) == 0));
    }

    private static final int MIN_CH_2G_BAND = 1;
    private static final int MAX_CH_2G_BAND = 14;
    private static final int MIN_CH_5G_BAND = 34;
    private static final int MAX_CH_5G_BAND = 196;
    private static final int MIN_CH_6G_BAND = 1;
    private static final int MAX_CH_6G_BAND = 253;
    private static final int MIN_CH_60G_BAND = 1;
    private static final int MAX_CH_60G_BAND = 6;

    /**
     * Requires to configure MAC randomization setting to None when configuring BSSID.
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
    private static final long FORCE_MUTUAL_EXCLUSIVE_BSSID_MAC_RAMDONIZATION_SETTING = 215656264L;

    /**
     * Removes zero support on
     * {@link android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)}.
     *
     * @hide
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
    public static final long REMOVE_ZERO_FOR_TIMEOUT_SETTING = 213289672L;

    private static boolean isChannelBandPairValid(int channel, @BandType int band) {
        switch (band) {
            case BAND_2GHZ:
                if (channel < MIN_CH_2G_BAND || channel >  MAX_CH_2G_BAND) {
                    return false;
                }
                break;

            case BAND_5GHZ:
                if (channel < MIN_CH_5G_BAND || channel >  MAX_CH_5G_BAND) {
                    return false;
                }
                break;

            case BAND_6GHZ:
                if (channel < MIN_CH_6G_BAND || channel >  MAX_CH_6G_BAND) {
                    return false;
                }
                break;

            case BAND_60GHZ:
                if (channel < MIN_CH_60G_BAND || channel >  MAX_CH_60G_BAND) {
                    return false;
                }
                break;

            default:
                return false;
        }
        return true;
    }

    /**
     * SSID for the AP, or null for a framework-determined SSID.
     */
    private final @Nullable WifiSsid mWifiSsid;

    /**
     * BSSID for the AP, or null to use a framework-determined BSSID.
     */
    private final @Nullable MacAddress mBssid;

    /**
     * Vendor elements for the AP, structured as dd+len+elements
     */
    private final @NonNull List<ScanResult.InformationElement> mVendorElements;

    /**
     * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on
     * the security type.
     */
    private final @Nullable String mPassphrase;

    /**
     * This is a network that does not broadcast its SSID, so an
     * SSID-specific probe request must be used for scans.
     */
    private final boolean mHiddenSsid;

    /**
     * The operating channels of the dual APs.
     *
     * The SparseIntArray that consists the band and the channel of matching the band.
     */
    @NonNull
    private final SparseIntArray mChannels;

    /**
     * The set of allowed channels in 2.4GHz band to select from using ACS (Automatic Channel
     * Selection) algorithm.
     *
     * Requires the driver to support {@link SoftApCapability#SOFTAP_FEATURE_ACS_OFFLOAD}.
     * Otherwise, this set will be ignored.
     *
     * If the set is empty, then all channels in 2.4GHz band are allowed.
     */
    private final @NonNull Set<Integer> mAllowedAcsChannels2g;

    /**
     * The set of allowed channels in 5GHz band to select from using ACS (Automatic Channel
     * Selection) algorithm.
     *
     * Requires the driver to support {@link SoftApCapability#SOFTAP_FEATURE_ACS_OFFLOAD}.
     * Otherwise, this set will be ignored.
     *
     * If the set is empty, then all channels in 5GHz are allowed.
     */
    private final @NonNull Set<Integer> mAllowedAcsChannels5g;

    /**
     * The set of allowed channels in 6GHz band to select from using ACS (Automatic Channel
     * Selection) algorithm.
     *
     * Requires the driver to support {@link SoftApCapability#SOFTAP_FEATURE_ACS_OFFLOAD}.
     * Otherwise, this set will be ignored.
     *
     * If the set is empty, then all channels in 6GHz are allowed.
     */
    private final @NonNull Set<Integer> mAllowedAcsChannels6g;

    /**
     * The maximum channel bandwidth for SoftAp operation
     *
     * Default value is SoftApInfo#CHANNEL_WIDTH_AUTO which means the channel bandwidth
     * is to be selected by the chip based on device capabilities.
     * <p>
     *
     * Valid values: {@link SoftApInfo#CHANNEL_WIDTH_AUTO},
     * {@link SoftApInfo#CHANNEL_WIDTH_20MHZ}, {@link SoftApInfo#CHANNEL_WIDTH_40MHZ},
     * {@link SoftApInfo#CHANNEL_WIDTH_80MHZ}, {@link SoftApInfo#CHANNEL_WIDTH_160MHZ},
     * {@link SoftApInfo#CHANNEL_WIDTH_320MHZ}
     *
     */
    private final @WifiAnnotations.Bandwidth int mMaxChannelBandwidth;

    /**
     * The maximim allowed number of clients that can associate to the AP.
     */
    private final int mMaxNumberOfClients;

    /**
     * The operating security type of the AP.
     * One of the following security types:
     * {@link #SECURITY_TYPE_OPEN},
     * {@link #SECURITY_TYPE_WPA2_PSK},
     * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
     * {@link #SECURITY_TYPE_WPA3_SAE},
     * {@link #SECURITY_TYPE_WPA3_OWE_TRANSITION},
     * {@link #SECURITY_TYPE_WPA3_OWE}
     */
    private final @SecurityType int mSecurityType;

    /**
     * The flag to indicate client need to authorize by user
     * when client is connecting to AP.
     */
    private final boolean mClientControlByUser;

    /**
     * The list of blocked client that can't associate to the AP.
     */
    private final List<MacAddress> mBlockedClientList;

    /**
     * The list of allowed client that can associate to the AP.
     */
    private final List<MacAddress> mAllowedClientList;

    /**
     * Whether auto shutdown of soft AP is enabled or not.
     */
    private final boolean mAutoShutdownEnabled;

    /**
     * Delay in milliseconds before shutting down soft AP when
     * there are no connected devices.
     */
    private final long mShutdownTimeoutMillis;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"RANDOMIZATION_"}, value = {
            RANDOMIZATION_NONE,
            RANDOMIZATION_PERSISTENT,
            RANDOMIZATION_NON_PERSISTENT})
    public @interface MacRandomizationSetting {}

    /**
     * Use the factory MAC address as the BSSID of the AP.
     *
     * @hide
     */
    @SystemApi
    public static final int RANDOMIZATION_NONE = 0;

    /**
     * Generate a persistent randomized MAC address as the BSSID of the AP.
     * The MAC address is persisted per SSID - i.e. as long as the SSID of the AP doesn't change
     * then it will have a persistent MAC address (which is initially random and is not the factory
     * MAC address).
     *
     * @hide
     */
    @SystemApi
    public static final int RANDOMIZATION_PERSISTENT = 1;

    /**
     * Generate a randomized MAC address as the BSSID of the AP. The MAC address is not persisted
     * - it is re-generated every time the AP is re-enabled.
     * @hide
     */
    @SystemApi
    public static final int RANDOMIZATION_NON_PERSISTENT = 2;

    /**
     * Level of MAC randomization for the AP BSSID.
     */
    @MacRandomizationSetting
    private int mMacRandomizationSetting;


    /**
     * Whether opportunistic shutdown of an instance in bridged AP is enabled or not.
     */
    private boolean mBridgedModeOpportunisticShutdownEnabled;

    /**
     * Whether 802.11ax AP is enabled or not.
     */
    private boolean mIeee80211axEnabled;

    /**
     * Whether 802.11be AP is enabled or not.
     */
    private boolean mIeee80211beEnabled;

    /**
     * Whether the current configuration is configured by user or not.
     */
    private boolean mIsUserConfiguration;

    /**
     * Randomized MAC address to use with this configuration when MAC randomization setting
     * is {@link #RANDOMIZATION_PERSISTENT}.
     */
    private final @Nullable MacAddress mPersistentRandomizedMacAddress;

    /**
     * Delay in milliseconds before shutting down an instance in bridged AP.
     */
    private final long mBridgedModeOpportunisticShutdownTimeoutMillis;

    /** List of {@link OuiKeyedData} providing vendor-specific configuration data. */
    private @NonNull List<OuiKeyedData> mVendorData;

    /**
     * Whether connected clients can communicate with each other or not.
     */
    private boolean mIsClientIsolationEnabled;

    /**
     * THe definition of security type OPEN.
     */
    public static final int SECURITY_TYPE_OPEN = 0;

    /**
     * The definition of security type WPA2-PSK.
     */
    public static final int SECURITY_TYPE_WPA2_PSK = 1;

    /**
     * The definition of security type WPA3-SAE Transition mode.
     */
    public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;

    /**
     * The definition of security type WPA3-SAE.
     */
    public static final int SECURITY_TYPE_WPA3_SAE = 3;

    /**
     * The definition of security type WPA3-OWE Transition.
     */
    public static final int SECURITY_TYPE_WPA3_OWE_TRANSITION = 4;

    /**
     * The definition of security type WPA3-OWE.
     */
    public static final int SECURITY_TYPE_WPA3_OWE = 5;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
        SECURITY_TYPE_OPEN,
        SECURITY_TYPE_WPA2_PSK,
        SECURITY_TYPE_WPA3_SAE_TRANSITION,
        SECURITY_TYPE_WPA3_SAE,
        SECURITY_TYPE_WPA3_OWE_TRANSITION,
        SECURITY_TYPE_WPA3_OWE,
    })
    public @interface SecurityType {}

    /** Private constructor for Builder and Parcelable implementation. */
    private SoftApConfiguration(
            @Nullable WifiSsid ssid,
            @Nullable MacAddress bssid,
            @Nullable String passphrase,
            boolean hiddenSsid,
            @NonNull SparseIntArray channels,
            @SecurityType int securityType,
            int maxNumberOfClients,
            boolean shutdownTimeoutEnabled,
            long shutdownTimeoutMillis,
            boolean clientControlByUser,
            @NonNull List<MacAddress> blockedList,
            @NonNull List<MacAddress> allowedList,
            int macRandomizationSetting,
            boolean bridgedModeOpportunisticShutdownEnabled,
            boolean ieee80211axEnabled,
            boolean ieee80211beEnabled,
            boolean isUserConfiguration,
            long bridgedModeOpportunisticShutdownTimeoutMillis,
            @NonNull List<ScanResult.InformationElement> vendorElements,
            @Nullable MacAddress persistentRandomizedMacAddress,
            @NonNull Set<Integer> allowedAcsChannels24g,
            @NonNull Set<Integer> allowedAcsChannels5g,
            @NonNull Set<Integer> allowedAcsChannels6g,
            @WifiAnnotations.Bandwidth int maxChannelBandwidth,
            @Nullable List<OuiKeyedData> vendorData,
            boolean isClientIsolationEnabled) {
        mWifiSsid = ssid;
        mBssid = bssid;
        mPassphrase = passphrase;
        mHiddenSsid = hiddenSsid;
        if (channels.size() != 0) {
            mChannels = channels.clone();
        } else {
            mChannels = new SparseIntArray(1);
            mChannels.put(BAND_2GHZ, 0);
        }
        mSecurityType = securityType;
        mMaxNumberOfClients = maxNumberOfClients;
        mAutoShutdownEnabled = shutdownTimeoutEnabled;
        mShutdownTimeoutMillis = shutdownTimeoutMillis;
        mClientControlByUser = clientControlByUser;
        mBlockedClientList = new ArrayList<>(blockedList);
        mAllowedClientList = new ArrayList<>(allowedList);
        mMacRandomizationSetting = macRandomizationSetting;
        mBridgedModeOpportunisticShutdownEnabled = bridgedModeOpportunisticShutdownEnabled;
        mIeee80211axEnabled = ieee80211axEnabled;
        mIeee80211beEnabled = ieee80211beEnabled;
        mIsUserConfiguration = isUserConfiguration;
        mBridgedModeOpportunisticShutdownTimeoutMillis =
                bridgedModeOpportunisticShutdownTimeoutMillis;
        mVendorElements = new ArrayList<>(vendorElements);
        mPersistentRandomizedMacAddress = persistentRandomizedMacAddress;
        mAllowedAcsChannels2g = new HashSet<>(allowedAcsChannels24g);
        mAllowedAcsChannels5g = new HashSet<>(allowedAcsChannels5g);
        mAllowedAcsChannels6g = new HashSet<>(allowedAcsChannels6g);
        mMaxChannelBandwidth = maxChannelBandwidth;
        mVendorData = new ArrayList<>(vendorData);
        mIsClientIsolationEnabled = isClientIsolationEnabled;
    }

    @Override
    public boolean equals(Object otherObj) {
        if (this == otherObj) {
            return true;
        }
        if (!(otherObj instanceof SoftApConfiguration)) {
            return false;
        }
        SoftApConfiguration other = (SoftApConfiguration) otherObj;
        return Objects.equals(mWifiSsid, other.mWifiSsid)
                && Objects.equals(mBssid, other.mBssid)
                && Objects.equals(mPassphrase, other.mPassphrase)
                && mHiddenSsid == other.mHiddenSsid
                && mChannels.toString().equals(other.mChannels.toString())
                && mSecurityType == other.mSecurityType
                && mMaxNumberOfClients == other.mMaxNumberOfClients
                && mAutoShutdownEnabled == other.mAutoShutdownEnabled
                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
                && mClientControlByUser == other.mClientControlByUser
                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
                && Objects.equals(mAllowedClientList, other.mAllowedClientList)
                && mMacRandomizationSetting == other.mMacRandomizationSetting
                && mBridgedModeOpportunisticShutdownEnabled
                        == other.mBridgedModeOpportunisticShutdownEnabled
                && mIeee80211axEnabled == other.mIeee80211axEnabled
                && mIeee80211beEnabled == other.mIeee80211beEnabled
                && mIsUserConfiguration == other.mIsUserConfiguration
                && mBridgedModeOpportunisticShutdownTimeoutMillis
                        == other.mBridgedModeOpportunisticShutdownTimeoutMillis
                && Objects.equals(mVendorElements, other.mVendorElements)
                && Objects.equals(
                        mPersistentRandomizedMacAddress, other.mPersistentRandomizedMacAddress)
                && Objects.equals(mAllowedAcsChannels2g, other.mAllowedAcsChannels2g)
                && Objects.equals(mAllowedAcsChannels5g, other.mAllowedAcsChannels5g)
                && Objects.equals(mAllowedAcsChannels6g, other.mAllowedAcsChannels6g)
                && mMaxChannelBandwidth == other.mMaxChannelBandwidth
                && Objects.equals(mVendorData, other.mVendorData)
                && mIsClientIsolationEnabled == other.mIsClientIsolationEnabled;
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                mWifiSsid,
                mBssid,
                mPassphrase,
                mHiddenSsid,
                mChannels.toString(),
                mSecurityType,
                mMaxNumberOfClients,
                mAutoShutdownEnabled,
                mShutdownTimeoutMillis,
                mClientControlByUser,
                mBlockedClientList,
                mAllowedClientList,
                mMacRandomizationSetting,
                mBridgedModeOpportunisticShutdownEnabled,
                mIeee80211axEnabled,
                mIeee80211beEnabled,
                mIsUserConfiguration,
                mBridgedModeOpportunisticShutdownTimeoutMillis,
                mVendorElements,
                mPersistentRandomizedMacAddress,
                mAllowedAcsChannels2g,
                mAllowedAcsChannels5g,
                mAllowedAcsChannels6g,
                mMaxChannelBandwidth,
                mVendorData,
                mIsClientIsolationEnabled);
    }

    @Override
    public String toString() {
        StringBuilder sbuf = new StringBuilder();
        sbuf.append("ssid = ").append(mWifiSsid == null ? null : mWifiSsid.toString());
        if (mBssid != null) sbuf.append(" \n bssid = ").append(mBssid.toString());
        sbuf.append(" \n Passphrase = ").append(
                TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
        sbuf.append(" \n HiddenSsid = ").append(mHiddenSsid);
        sbuf.append(" \n Channels = ").append(mChannels);
        sbuf.append(" \n SecurityType = ").append(getSecurityType());
        sbuf.append(" \n MaxClient = ").append(mMaxNumberOfClients);
        sbuf.append(" \n AutoShutdownEnabled = ").append(mAutoShutdownEnabled);
        sbuf.append(" \n ShutdownTimeoutMillis = ").append(mShutdownTimeoutMillis);
        sbuf.append(" \n ClientControlByUser = ").append(mClientControlByUser);
        sbuf.append(" \n BlockedClientList = ").append(mBlockedClientList);
        sbuf.append(" \n AllowedClientList= ").append(mAllowedClientList);
        sbuf.append(" \n MacRandomizationSetting = ").append(mMacRandomizationSetting);
        sbuf.append(" \n BridgedModeInstanceOpportunisticEnabled = ")
                .append(mBridgedModeOpportunisticShutdownEnabled);
        sbuf.append(" \n BridgedModeOpportunisticShutdownTimeoutMillis = ")
                .append(mBridgedModeOpportunisticShutdownTimeoutMillis);
        sbuf.append(" \n Ieee80211axEnabled = ").append(mIeee80211axEnabled);
        sbuf.append(" \n Ieee80211beEnabled = ").append(mIeee80211beEnabled);
        sbuf.append(" \n isUserConfiguration = ").append(mIsUserConfiguration);
        sbuf.append(" \n vendorElements = ").append(mVendorElements);
        sbuf.append(" \n mPersistentRandomizedMacAddress = ")
                .append(mPersistentRandomizedMacAddress);
        sbuf.append(" \n mAllowedAcsChannels2g = ").append(mAllowedAcsChannels2g);
        sbuf.append(" \n mAllowedAcsChannels5g = ").append(mAllowedAcsChannels5g);
        sbuf.append(" \n mAllowedAcsChannels6g = ").append(mAllowedAcsChannels6g);
        sbuf.append(" \n mMaxChannelBandwidth = ").append(mMaxChannelBandwidth);
        sbuf.append(" \n mVendorData = ").append(mVendorData);
        sbuf.append(" \n mIsClientIsolationEnabled = ").append(mIsClientIsolationEnabled);
        return sbuf.toString();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeParcelable(mWifiSsid, 0);
        dest.writeParcelable(mBssid, flags);
        dest.writeString(mPassphrase);
        dest.writeBoolean(mHiddenSsid);
        writeSparseIntArray(dest, mChannels);
        dest.writeInt(mSecurityType);
        dest.writeInt(mMaxNumberOfClients);
        dest.writeBoolean(mAutoShutdownEnabled);
        dest.writeLong(mShutdownTimeoutMillis);
        dest.writeBoolean(mClientControlByUser);
        dest.writeTypedList(mBlockedClientList);
        dest.writeTypedList(mAllowedClientList);
        dest.writeInt(mMacRandomizationSetting);
        dest.writeBoolean(mBridgedModeOpportunisticShutdownEnabled);
        dest.writeBoolean(mIeee80211axEnabled);
        dest.writeBoolean(mIeee80211beEnabled);
        dest.writeBoolean(mIsUserConfiguration);
        dest.writeLong(mBridgedModeOpportunisticShutdownTimeoutMillis);
        dest.writeTypedList(mVendorElements);
        dest.writeParcelable(mPersistentRandomizedMacAddress, flags);
        writeHashSetInt(dest, mAllowedAcsChannels2g);
        writeHashSetInt(dest, mAllowedAcsChannels5g);
        writeHashSetInt(dest, mAllowedAcsChannels6g);
        dest.writeInt(mMaxChannelBandwidth);
        dest.writeList(mVendorData);
        dest.writeBoolean(mIsClientIsolationEnabled);
    }

    /* Reference from frameworks/base/core/java/android/os/Parcel.java */
    private static void writeSparseIntArray(@NonNull Parcel dest,
            @Nullable SparseIntArray val) {
        if (val == null) {
            dest.writeInt(-1);
            return;
        }
        int n = val.size();
        dest.writeInt(n);
        int i = 0;
        while (i < n) {
            dest.writeInt(val.keyAt(i));
            dest.writeInt(val.valueAt(i));
            i++;
        }
    }

    /* Reference from frameworks/base/core/java/android/os/Parcel.java */
    @NonNull
    private static SparseIntArray readSparseIntArray(@NonNull Parcel in) {
        int n = in.readInt();
        if (n < 0) {
            return new SparseIntArray();
        }
        SparseIntArray sa = new SparseIntArray(n);
        while (n > 0) {
            int key = in.readInt();
            int value = in.readInt();
            sa.append(key, value);
            n--;
        }
        return sa;
    }

    /* Write HashSet<Integer> into Parcel */
    private static void writeHashSetInt(@NonNull Parcel dest, @NonNull Set<Integer> set) {
        if (set.isEmpty()) {
            dest.writeInt(-1);
            return;
        }

        dest.writeInt(set.size());
        for (int val : set) {
            dest.writeInt(val);
        }
    }

    /* Read HashSet<Integer> from Parcel */
    @NonNull
    private static Set<Integer> readHashSetInt(@NonNull Parcel in) {
        Set<Integer> set = new HashSet<>();
        int len = in.readInt();
        if (len < 0) {
            return set;
        }

        for (int i = 0; i < len; i++) {
            set.add(in.readInt());
        }
        return set;
    }

    /* Read List<OuiKeyedData> from Parcel */
    @NonNull
    private static List<OuiKeyedData> readOuiKeyedDataList(@NonNull Parcel in) {
        List<OuiKeyedData> dataList = new ArrayList<>();
        if (SdkLevel.isAtLeastT()) {
            in.readList(dataList, OuiKeyedData.class.getClassLoader(), OuiKeyedData.class);
        } else {
            in.readList(dataList, OuiKeyedData.class.getClassLoader());
        }
        return dataList;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @NonNull
    public static final Creator<SoftApConfiguration> CREATOR =
            new Creator<SoftApConfiguration>() {
                @Override
                public SoftApConfiguration createFromParcel(Parcel in) {
                    return new SoftApConfiguration(
                            in.readParcelable(WifiSsid.class.getClassLoader()),
                            in.readParcelable(MacAddress.class.getClassLoader()),
                            in.readString(),
                            in.readBoolean(),
                            readSparseIntArray(in),
                            in.readInt(),
                            in.readInt(),
                            in.readBoolean(),
                            in.readLong(),
                            in.readBoolean(),
                            in.createTypedArrayList(MacAddress.CREATOR),
                            in.createTypedArrayList(MacAddress.CREATOR),
                            in.readInt(),
                            in.readBoolean(),
                            in.readBoolean(),
                            in.readBoolean(),
                            in.readBoolean(),
                            in.readLong(),
                            in.createTypedArrayList(ScanResult.InformationElement.CREATOR),
                            in.readParcelable(MacAddress.class.getClassLoader()),
                            readHashSetInt(in),
                            readHashSetInt(in),
                            readHashSetInt(in),
                            in.readInt(),
                            readOuiKeyedDataList(in),
                            in.readBoolean());
                }

                @Override
                public SoftApConfiguration[] newArray(int size) {
                    return new SoftApConfiguration[size];
                }
            };

    /**
     * Return the UTF-8 String set to be the SSID for the AP. If the SSID cannot be decoded as
     * UTF-8, then this will return {@link WifiManager#UNKNOWN_SSID}.
     *
     * @deprecated Use {@link #getWifiSsid()} instead.
     */
    @Nullable
    @Deprecated
    public String getSsid() {
        if (mWifiSsid == null) {
            return null;
        }
        CharSequence utf8Text = mWifiSsid.getUtf8Text();
        return utf8Text != null ? utf8Text.toString() : WifiManager.UNKNOWN_SSID;
    }

    /**
     * Return WifiSsid set to be the SSID for the AP.
     */
    @Nullable
    public WifiSsid getWifiSsid() {
        return mWifiSsid;
    }

    /**
     * Return VendorElements for the AP.
     * @hide
     */
    @NonNull
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    @SystemApi
    public List<ScanResult.InformationElement> getVendorElements() {
        if (!SdkLevel.isAtLeastT()) {
            throw new UnsupportedOperationException();
        }
        return getVendorElementsInternal();
    }

    /**
     * @see #getVendorElements()
     * @hide
     */
    public List<ScanResult.InformationElement> getVendorElementsInternal() {
        return new ArrayList<>(mVendorElements);
    }

    /**
     * Returns MAC address set to be BSSID for the AP.
     */
    @Nullable
    public MacAddress getBssid() {
        return mBssid;
    }

    /**
     * Returns String set to be passphrase for current AP.
     */
    @Nullable
    public String getPassphrase() {
        return mPassphrase;
    }

    /**
     * Returns Boolean set to be indicate hidden (true: doesn't broadcast its SSID) or
     * not (false: broadcasts its SSID) for the AP.
     */
    public boolean isHiddenSsid() {
        return mHiddenSsid;
    }

    /**
     * Returns band type set to be the band for the AP.
     *
     * One or combination of {@code BAND_}, for instance
     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, or {@code BAND_2GHZ | BAND_5GHZ}.
     *
     * Note: Returns the lowest band when more than one band is set.
     * Use {@link #getChannels()} to get dual bands setting.
     *
     * See also {@link Builder#setBand(int)}.
     *
     * @deprecated This API is deprecated. Use {@link #getChannels()} instead.
     * @hide
     */
    @Deprecated
    @SystemApi
    public @BandType int getBand() {
        return mChannels.keyAt(0);
    }

    /**
     * Returns a sorted array in ascending order that consists of the configured band types
     * for the APs.
     *
     * The band type is one or combination of {@code BAND_}, for instance
     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, or {@code BAND_2GHZ | BAND_5GHZ}.
     *
     * Note: return array may only include one band when current setting is single AP mode.
     * See also {@link Builder#setBands(int[])}.
     *
     * @hide
     */
    @Keep
    public @NonNull int[] getBands() {
        int[] bands = new int[mChannels.size()];
        for (int i = 0; i < bands.length; i++) {
            bands[i] = mChannels.keyAt(i);
        }
        return bands;
    }

    /**
     * Returns Integer set to be the channel for the AP.
     *
     * Note: Returns the channel which associated to the lowest band if more than one channel
     * is set. Use {@link Builder#getChannels()} to get dual channel setting.
     * See also {@link Builder#setChannel(int, int)}.
     *
     * @deprecated This API is deprecated. Use {@link #getChannels()} instead.
     * @hide
     */
    @Deprecated
    @SystemApi
    public int getChannel() {
        return mChannels.valueAt(0);
    }


    /**
     * Returns SparseIntArray (key: {@code BandType} , value: channel) that consists of
     * the configured bands and channels for the AP(s).
     *
     * The returned channel value is Wi-Fi channel numbering.
     * Reference the Wi-Fi channel numbering and the channelization in IEEE 802.11-2016
     * specifications, section 17.3.8.4.2, 17.3.8.4.3 and Table 15-6.
     *
     * Note: return array may only include one channel when current setting is single AP mode.
     * See also {@link Builder#setChannels(SparseIntArray)}.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    @RequiresApi(Build.VERSION_CODES.S)
    public @NonNull SparseIntArray getChannels() {
        if (!SdkLevel.isAtLeastS()) {
            throw new UnsupportedOperationException();
        }
        return mChannels.clone();
    }

    /**
     * Get security type params which depends on which security passphrase to set.
     *
     * @return One of:
     * {@link #SECURITY_TYPE_OPEN},
     * {@link #SECURITY_TYPE_WPA2_PSK},
     * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
     * {@link #SECURITY_TYPE_WPA3_SAE},
     * {@link #SECURITY_TYPE_WPA3_OWE_TRANSITION},
     * {@link #SECURITY_TYPE_WPA3_OWE}
     */
    public @SecurityType int getSecurityType() {
        return mSecurityType;
    }

    /**
     * Returns the maximum number of clients that can associate to the AP.
     * See also {@link Builder#setMaxNumberOfClients(int)}.
     *
     * @hide
     */
    @SystemApi
    public int getMaxNumberOfClients() {
        return mMaxNumberOfClients;
    }

    /**
     * Returns whether auto shutdown is enabled or not.
     * The Soft AP will shutdown when there are no devices associated to it for
     * the timeout duration. See also {@link Builder#setAutoShutdownEnabled(boolean)}.
     *
     * @hide
     */
    @SystemApi
    public boolean isAutoShutdownEnabled() {
        return mAutoShutdownEnabled;
    }

    /**
     * Returns the shutdown timeout in milliseconds.
     * The Soft AP will shutdown when there are no devices associated to it for
     * the timeout duration. See also {@link Builder#setShutdownTimeoutMillis(long)}.
     *
     * @hide
     */
    @SystemApi
    public long getShutdownTimeoutMillis() {
        if (!CompatChanges.isChangeEnabled(
                REMOVE_ZERO_FOR_TIMEOUT_SETTING) && mShutdownTimeoutMillis == DEFAULT_TIMEOUT) {
            // For legacy application, return 0 when setting is DEFAULT_TIMEOUT.
            return 0;
        }
        return mShutdownTimeoutMillis;
    }

    /**
     * Returns a flag indicating whether clients need to be pre-approved by the user.
     * (true: authorization required) or not (false: not required).
     * See also {@link Builder#setClientControlByUserEnabled(Boolean)}.
     *
     * @return true when client isolation is enable.
     *
     * @hide
     */
    @SystemApi
    public boolean isClientControlByUserEnabled() {
        return mClientControlByUser;
    }

    /**
     * Returns List of clients which aren't allowed to associate to the AP.
     *
     * Clients are configured using {@link Builder#setBlockedClientList(List)}
     *
     * @hide
     */
    @NonNull
    @SystemApi
    public List<MacAddress> getBlockedClientList() {
        return mBlockedClientList;
    }

    /**
     * List of clients which are allowed to associate to the AP.
     * Clients are configured using {@link Builder#setAllowedClientList(List)}
     *
     * @hide
     */
    @NonNull
    @SystemApi
    public List<MacAddress> getAllowedClientList() {
        return mAllowedClientList;
    }

    /**
     * Returns the level of MAC randomization for the AP BSSID.
     * See also {@link Builder#setMacRandomizationSetting(int)}.
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.S)
    @SystemApi
    @MacRandomizationSetting
    public int getMacRandomizationSetting() {
        if (!SdkLevel.isAtLeastS()) {
            throw new UnsupportedOperationException();
        }
        return getMacRandomizationSettingInternal();
    }

    /**
     * @hide
     */
    @MacRandomizationSetting
    public int getMacRandomizationSettingInternal() {
        return mMacRandomizationSetting;
    }

    /**
     * Returns whether opportunistic shutdown of an instance in bridged AP is enabled or not.
     *
     * See also {@link Builder#setBridgedModeOpportunisticShutdownEnabled(boolean}}
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.S)
    @SystemApi
    public boolean isBridgedModeOpportunisticShutdownEnabled() {
        if (!SdkLevel.isAtLeastS()) {
            throw new UnsupportedOperationException();
        }
        return isBridgedModeOpportunisticShutdownEnabledInternal();
    }

    /**
     * @see #isBridgedModeOpportunisticShutdownEnabled()
     * @hide
     */
    public boolean isBridgedModeOpportunisticShutdownEnabledInternal() {
        return mBridgedModeOpportunisticShutdownEnabled;
    }

    /**
     * @see #isIeee80211axEnabled()
     * @hide
     */
    public boolean isIeee80211axEnabledInternal() {
        return mIeee80211axEnabled;
    }

    /**
     * Returns whether or not 802.11ax is enabled on the SoftAP.
     * This is an indication that if the device support 802.11ax AP then to enable or disable
     * that feature. If the device does not support 802.11ax AP then this flag is ignored.
     * See also {@link Builder#setIeee80211axEnabled(boolean}}
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.S)
    @SystemApi
    public boolean isIeee80211axEnabled() {
        if (!SdkLevel.isAtLeastS()) {
            throw new UnsupportedOperationException();
        }
        return isIeee80211axEnabledInternal();
    }

    /**
     * Returns whether or not the Soft AP is configured to enable 802.11be.
     * This is an indication that if the device support 802.11be AP then to enable or disable
     * that feature. If the device does not support 802.11be AP then this flag is ignored.
     * See also {@link Builder#setIeee80211beEnabled(boolean}}
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    @SystemApi
    public boolean isIeee80211beEnabled() {
        if (!SdkLevel.isAtLeastT()) {
            throw new UnsupportedOperationException();
        }
        return mIeee80211beEnabled;
    }

    /**
     * Returns the allowed channels for ACS in a selected band.
     *
     * If an empty array is returned, then all channels in that band are allowed
     * The channels are configured using {@link Builder#setAllowedAcsChannels(int, int[])}
     *
     * @param band one of the following band types:
     * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
     *
     * @return array of the allowed channels for ACS in that band
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    @NonNull
    @SystemApi
    public int[] getAllowedAcsChannels(@BandType int band) {
        if (!SdkLevel.isAtLeastT()) {
            throw new UnsupportedOperationException();
        }
        switch(band) {
            case BAND_2GHZ:
                return mAllowedAcsChannels2g.stream().mapToInt(Integer::intValue).toArray();
            case BAND_5GHZ:
                return mAllowedAcsChannels5g.stream().mapToInt(Integer::intValue).toArray();
            case BAND_6GHZ:
                return mAllowedAcsChannels6g.stream().mapToInt(Integer::intValue).toArray();
            default:
                throw new IllegalArgumentException("getAllowedAcsChannels: Invalid band: " + band);
        }
    }

    /**
     * Returns configured maximum channel bandwidth for the SoftAp connection.
     *
     * If not configured, it will return {@link SoftApInfo#CHANNEL_WIDTH_AUTO}
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    @SystemApi
    public @WifiAnnotations.Bandwidth int getMaxChannelBandwidth() {
        if (!SdkLevel.isAtLeastT()) {
            throw new UnsupportedOperationException();
        }
        return mMaxChannelBandwidth;
    }

    /**
     * Returns whether or not the {@link SoftApConfiguration} was configured by the user
     * (as opposed to the default system configuration).
     * <p>
     * The {@link SoftApConfiguration} is considered user edited once the
     * {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)} is called
     * - whether or not that configuration is the same as the default system configuration!
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.S)
    @SystemApi
    public boolean isUserConfiguration() {
        if (!SdkLevel.isAtLeastS()) {
            throw new UnsupportedOperationException();
        }
        return isUserConfigurationInternal();
    }

    /**
     * Returns the randomized MAC address to be used by this configuration.
     *
     * The Soft AP may be configured to use a persistent randomized MAC address with
     * {@link Builder#setMacRandomizationSetting(int)}. This method returns the persistent
     * randomized MAC address which will be used for the Soft AP controlled by this configuration.
     *
     * @hide
     */
    @SystemApi
    public @NonNull MacAddress getPersistentRandomizedMacAddress() {
        return mPersistentRandomizedMacAddress;
    }

    /**
     * @hide
     */
    public boolean isUserConfigurationInternal() {
        return mIsUserConfiguration;
    }

    /**
     * Returns the bridged mode opportunistic shutdown timeout in milliseconds.
     * An instance in bridged AP will shutdown when there is no device associated to it for
     * the timeout duration. See also
     * {@link Builder#setBridgedModeOpportunisticShutdownTimeoutMillis(long)}.
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    @SystemApi
    public long getBridgedModeOpportunisticShutdownTimeoutMillis() {
        if (!SdkLevel.isAtLeastT()) {
            throw new UnsupportedOperationException();
        }
        return mBridgedModeOpportunisticShutdownTimeoutMillis;
    }


    /**
     * @hide
     */
    public long getBridgedModeOpportunisticShutdownTimeoutMillisInternal() {
        return mBridgedModeOpportunisticShutdownTimeoutMillis;
    }

    /**
     * Return the vendor-provided configuration data, if it exists. See also {@link
     * Builder#setVendorData(List)}
     *
     * @return Vendor configuration data, or empty list if it does not exist.
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
    @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
    @NonNull
    @SystemApi
    public List<OuiKeyedData> getVendorData() {
        if (!SdkLevel.isAtLeastV()) {
            throw new UnsupportedOperationException();
        }
        return mVendorData;
    }

    /**
     * Returns whether client isolation is enabled or not.
     *
     * Client isolation is used to disallow a connected Soft AP
     * client to communicate with other connected clients.
     *
     * @hide
     */
    @RequiresApi(Build.VERSION_CODES.BAKLAVA)
    @FlaggedApi(Flags.FLAG_AP_ISOLATE)
    @SystemApi
    public boolean isClientIsolationEnabled() {
        if (!Environment.isSdkAtLeastB()) {
            throw new UnsupportedOperationException();
        }
        return mIsClientIsolationEnabled;
    }

    /**
     * Returns a {@link WifiConfiguration} representation of this {@link SoftApConfiguration}.
     * Note that SoftApConfiguration may contain configuration which is cannot be represented
     * by the legacy WifiConfiguration, in such cases a null will be returned.
     *
     * To maintain legacy behavior, the SSID of the WifiConfiguration will be the UTF-8
     * representation of the SSID without double quotes, as opposed to the double-quoted UTF-8
     * format documented in {@link WifiConfiguration#SSID}. If the SSID cannot be decoded as UTF-8,
     * then the SSID of the WifiConfiguration will be {@link WifiManager#UNKNOWN_SSID}.
     *
     * <li> SoftAp band in {@link WifiConfiguration.apBand} only supports
     * 2GHz, 5GHz, 2GHz+5GHz bands, so conversion is limited to these bands. </li>
     *
     * <li> SoftAp security type in {@link WifiConfiguration.KeyMgmt} only supports
     * NONE, WPA2_PSK, so conversion is limited to these security type.</li>
     * @hide
     */
    @Nullable
    @SystemApi
    public WifiConfiguration toWifiConfiguration() {
        WifiConfiguration wifiConfig = new WifiConfiguration();
        CharSequence utf8Text = mWifiSsid != null ? mWifiSsid.getUtf8Text() : null;
        wifiConfig.SSID = utf8Text != null ? utf8Text.toString() : WifiManager.UNKNOWN_SSID;
        wifiConfig.preSharedKey = mPassphrase;
        wifiConfig.hiddenSSID = mHiddenSsid;
        wifiConfig.apChannel = getChannel();
        switch (mSecurityType) {
            case SECURITY_TYPE_OPEN:
                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                break;
            case SECURITY_TYPE_WPA2_PSK:
            case SECURITY_TYPE_WPA3_SAE_TRANSITION:
                wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK);
                break;
            default:
                Log.e(TAG, "Convert fail, unsupported security type :" + mSecurityType);
                return null;
        }

        switch (getBand()) {
            case BAND_2GHZ:
                wifiConfig.apBand  = WifiConfiguration.AP_BAND_2GHZ;
                break;
            case BAND_5GHZ:
                wifiConfig.apBand  = WifiConfiguration.AP_BAND_5GHZ;
                break;
            case BAND_2GHZ | BAND_5GHZ:
                wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
                break;
            case BAND_ANY:
                wifiConfig.apBand  = WifiConfiguration.AP_BAND_ANY;
                break;
            default:
                Log.e(TAG, "Convert fail, unsupported band setting :" + getBand());
                return null;
        }
        return wifiConfig;
    }

    /**
     * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
     * Soft AP.
     *
     * All fields are optional. By default, SSID and BSSID are automatically chosen by the
     * framework, and an open network is created.
     */
    @FlaggedApi(Flags.FLAG_PUBLIC_BANDS_FOR_LOHS)
    public static final class Builder {
        private WifiSsid mWifiSsid;
        private MacAddress mBssid;
        private String mPassphrase;
        private boolean mHiddenSsid;
        private SparseIntArray mChannels;
        private int mMaxNumberOfClients;
        private int mSecurityType;
        private boolean mAutoShutdownEnabled;
        private long mShutdownTimeoutMillis;
        private boolean mClientControlByUser;
        private List<MacAddress> mBlockedClientList;
        private List<MacAddress> mAllowedClientList;
        private int mMacRandomizationSetting;
        private boolean mBridgedModeOpportunisticShutdownEnabled;
        private boolean mIeee80211axEnabled;
        private boolean mIeee80211beEnabled;
        private boolean mIsUserConfiguration;
        private long mBridgedModeOpportunisticShutdownTimeoutMillis;
        private List<ScanResult.InformationElement> mVendorElements;
        private MacAddress mPersistentRandomizedMacAddress;
        private Set<Integer> mAllowedAcsChannels2g;
        private Set<Integer> mAllowedAcsChannels5g;
        private Set<Integer> mAllowedAcsChannels6g;
        private @WifiAnnotations.Bandwidth int mMaxChannelBandwidth;
        private @Nullable List<OuiKeyedData> mVendorData;
        private boolean mIsClientIsolationEnabled;

        /**
         * Constructs a Builder with default values (see {@link Builder}).
         */
        public Builder() {
            mWifiSsid = null;
            mBssid = null;
            mPassphrase = null;
            mHiddenSsid = false;
            mChannels = new SparseIntArray(1);
            mChannels.put(BAND_2GHZ, 0);
            mMaxNumberOfClients = 0;
            mSecurityType = SECURITY_TYPE_OPEN;
            mAutoShutdownEnabled = true; // enabled by default.
            mShutdownTimeoutMillis = DEFAULT_TIMEOUT;
            mClientControlByUser = false;
            mBlockedClientList = new ArrayList<>();
            mAllowedClientList = new ArrayList<>();
            if (SdkLevel.isAtLeastT()) {
                mMacRandomizationSetting = RANDOMIZATION_NON_PERSISTENT;
            } else {
                mMacRandomizationSetting = RANDOMIZATION_PERSISTENT;
            }
            mBridgedModeOpportunisticShutdownEnabled = true;
            mIeee80211axEnabled = true;
            mIeee80211beEnabled = true;
            mIsUserConfiguration = true;
            mBridgedModeOpportunisticShutdownTimeoutMillis = DEFAULT_TIMEOUT;
            mVendorElements = new ArrayList<>();
            mPersistentRandomizedMacAddress = null;
            mAllowedAcsChannels2g = new HashSet<>();
            mAllowedAcsChannels5g = new HashSet<>();
            mAllowedAcsChannels6g = new HashSet<>();
            mMaxChannelBandwidth = SoftApInfo.CHANNEL_WIDTH_AUTO;
            mVendorData = new ArrayList<>();
            mIsClientIsolationEnabled = false;
        }

        /**
         * Constructs a Builder initialized from an existing {@link SoftApConfiguration} instance.
         *
         * @hide
         */
        @SystemApi
        public Builder(@NonNull SoftApConfiguration other) {
            if (other == null) {
                Log.e(TAG, "Cannot provide a null SoftApConfiguration");
                return;
            }

            mWifiSsid = other.mWifiSsid;
            mBssid = other.mBssid;
            mPassphrase = other.mPassphrase;
            mHiddenSsid = other.mHiddenSsid;
            mChannels = other.mChannels.clone();
            mMaxNumberOfClients = other.mMaxNumberOfClients;
            mSecurityType = other.mSecurityType;
            mAutoShutdownEnabled = other.mAutoShutdownEnabled;
            mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
            mClientControlByUser = other.mClientControlByUser;
            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
            mMacRandomizationSetting = other.mMacRandomizationSetting;
            mBridgedModeOpportunisticShutdownEnabled =
                    other.mBridgedModeOpportunisticShutdownEnabled;
            mIeee80211axEnabled = other.mIeee80211axEnabled;
            mIeee80211beEnabled = other.mIeee80211beEnabled;
            mIsUserConfiguration = other.mIsUserConfiguration;
            mBridgedModeOpportunisticShutdownTimeoutMillis =
                    other.mBridgedModeOpportunisticShutdownTimeoutMillis;
            mVendorElements = new ArrayList<>(other.mVendorElements);
            mPersistentRandomizedMacAddress = other.mPersistentRandomizedMacAddress;
            mAllowedAcsChannels2g = new HashSet<>(other.mAllowedAcsChannels2g);
            mAllowedAcsChannels5g = new HashSet<>(other.mAllowedAcsChannels5g);
            mAllowedAcsChannels6g = new HashSet<>(other.mAllowedAcsChannels6g);
            mMaxChannelBandwidth = other.mMaxChannelBandwidth;
            if (SdkLevel.isAtLeastS() && mBssid != null) {
                // Auto set correct MAC randomization setting for the legacy SoftApConfiguration
                // to avoid the exception happen when framework (system server) copy
                // SoftApConfiguration.
                mMacRandomizationSetting = RANDOMIZATION_NONE;
            }
            mVendorData = new ArrayList<>(other.mVendorData);
            mIsClientIsolationEnabled = other.mIsClientIsolationEnabled;
        }

       /**
         * Builds the {@link SoftApConfiguration} without any check.
         *
         * @return A new {@link SoftApConfiguration}, as configured by previous method calls.
         *
         * @hide
         */
        @VisibleForTesting
        @NonNull
        public SoftApConfiguration buildWithoutCheck() {
            return new SoftApConfiguration(
                    mWifiSsid,
                    mBssid,
                    mPassphrase,
                    mHiddenSsid,
                    mChannels,
                    mSecurityType,
                    mMaxNumberOfClients,
                    mAutoShutdownEnabled,
                    mShutdownTimeoutMillis,
                    mClientControlByUser,
                    mBlockedClientList,
                    mAllowedClientList,
                    mMacRandomizationSetting,
                    mBridgedModeOpportunisticShutdownEnabled,
                    mIeee80211axEnabled,
                    mIeee80211beEnabled,
                    mIsUserConfiguration,
                    mBridgedModeOpportunisticShutdownTimeoutMillis,
                    mVendorElements,
                    mPersistentRandomizedMacAddress,
                    mAllowedAcsChannels2g,
                    mAllowedAcsChannels5g,
                    mAllowedAcsChannels6g,
                    mMaxChannelBandwidth,
                    mVendorData,
                    mIsClientIsolationEnabled);
        }

        /**
         * Builds the {@link SoftApConfiguration}.
         *
         * @return A new {@link SoftApConfiguration}, as configured by previous method calls.
         */
        @NonNull
        public SoftApConfiguration build() {
            for (MacAddress client : mAllowedClientList) {
                if (mBlockedClientList.contains(client)) {
                    throw new IllegalArgumentException("A MacAddress exist in both client list");
                }
            }

            // mMacRandomizationSetting supported from S.
            if (SdkLevel.isAtLeastS() && CompatChanges.isChangeEnabled(
                    FORCE_MUTUAL_EXCLUSIVE_BSSID_MAC_RAMDONIZATION_SETTING)
                    && mBssid != null && mMacRandomizationSetting != RANDOMIZATION_NONE) {
                throw new IllegalArgumentException("A BSSID had configured but MAC randomization"
                        + " setting is not NONE");
            }

            if (!CompatChanges.isChangeEnabled(
                    REMOVE_ZERO_FOR_TIMEOUT_SETTING) && mShutdownTimeoutMillis == DEFAULT_TIMEOUT) {
                mShutdownTimeoutMillis = 0; // Use 0 for legacy app.
            }

            if (SdkLevel.isAtLeastB() && !mIeee80211axEnabled) {
                // Force 11BE to false since 11ax has dependency with 11AX.
                mIeee80211beEnabled = false;
            }
            return new SoftApConfiguration(
                    mWifiSsid,
                    mBssid,
                    mPassphrase,
                    mHiddenSsid,
                    mChannels,
                    mSecurityType,
                    mMaxNumberOfClients,
                    mAutoShutdownEnabled,
                    mShutdownTimeoutMillis,
                    mClientControlByUser,
                    mBlockedClientList,
                    mAllowedClientList,
                    mMacRandomizationSetting,
                    mBridgedModeOpportunisticShutdownEnabled,
                    mIeee80211axEnabled,
                    mIeee80211beEnabled,
                    mIsUserConfiguration,
                    mBridgedModeOpportunisticShutdownTimeoutMillis,
                    mVendorElements,
                    mPersistentRandomizedMacAddress,
                    mAllowedAcsChannels2g,
                    mAllowedAcsChannels5g,
                    mAllowedAcsChannels6g,
                    mMaxChannelBandwidth,
                    mVendorData,
                    mIsClientIsolationEnabled);
        }

        /**
         * Specifies a UTF-8 SSID for the AP.
         * <p>
         * Null SSID only support when configure a local-only hotspot.
         * <p>
         * <li>If not set, defaults to null.</li>
         *
         * @param ssid SSID of valid Unicode characters, or null to have the SSID automatically
         *             chosen by the framework.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when the SSID is empty, not unicode, or if the byte
         *                                  representation is longer than 32 bytes.
         *
         * @deprecated Use {@link #setWifiSsid(WifiSsid)} instead.
         *
         * @hide
         */
        @NonNull
        @Deprecated
        @SystemApi
        public Builder setSsid(@Nullable String ssid) {
            if (ssid == null) {
                mWifiSsid = null;
                return this;
            }

            Preconditions.checkStringNotEmpty(ssid);
            Preconditions.checkArgument(StandardCharsets.UTF_8.newEncoder().canEncode(ssid));
            mWifiSsid = WifiSsid.fromUtf8Text(ssid);
            return this;
        }

        /**
         * Specifies an SSID for the AP in the form of WifiSsid.
         * <p>
         * Null SSID only support when configure a local-only hotspot.
         * <p>
         * <li>If not set, defaults to null.</li>
         *
         * @param wifiSsid SSID, or null ot have the SSID automatically chosen by the framework.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @SystemApi
        public Builder setWifiSsid(@Nullable WifiSsid wifiSsid) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }
            mWifiSsid = wifiSsid;
            return this;
        }

        /**
         * Specify vendor-specific information elements for the (Soft) AP to transmit in its beacons
         * and probe responses. Method also validates the structure and throws
         * IllegalArgumentException in cases when ID of IE is not 0xDD (221) or incoming list
         * contain duplicate elements.
         *
         * @param vendorElements VendorElements
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @SystemApi
        public Builder setVendorElements(
                @NonNull List<ScanResult.InformationElement> vendorElements) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }
            for (ScanResult.InformationElement e : vendorElements) {
                if (e.id != ScanResult.InformationElement.EID_VSA) {
                    throw new IllegalArgumentException("received InformationElement which is not "
                            + "related to VendorElements. VendorElement block should start with "
                            + HexEncoding.encodeToString(
                                    new byte[]{ (byte) ScanResult.InformationElement.EID_VSA }));
                }
            }
            final HashSet<ScanResult.InformationElement> set = new HashSet<>(vendorElements);
            if (set.size() < vendorElements.size()) {
                throw new IllegalArgumentException("vendor elements array contain duplicates. "
                        + "Please avoid passing duplicated and keep structure clean.");
            }
            mVendorElements = new ArrayList<>(vendorElements);
            return this;
        }

        /**
         * Specifies a BSSID for the AP.
         * <p>
         * <li>If not set, defaults to null.</li>
         *
         * When this method is called, the caller needs to configure MAC randomization settings to
         * {@link #RANDOMIZATION_NONE}. See {@link #setMacRandomizationSetting(int)} for details.
         *
         * If multiple bands are requested via {@link #setBands(int[])} or
         * {@link #setChannels(SparseIntArray)}, HAL will derive 2 MAC addresses since framework
         * only sends down 1 MAC address.
         *
         * An example (but different implementation may perform a different mapping):
         * <li>MAC address 1: copy value of MAC address,
         * and set byte 1 = (0xFF - BSSID[1])</li>
         * <li>MAC address 2: copy value of MAC address,
         * and set byte 2 = (0xFF - BSSID[2])</li>
         *
         * Example BSSID argument: e2:38:60:c4:0e:b7
         * Derived MAC address 1: e2:c7:60:c4:0e:b7
         * Derived MAC address 2: e2:38:9f:c4:0e:b7
         *
         * <p>
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION} to determine
         * whether or not this feature is supported.
         *
         * @param bssid BSSID, or null to have the BSSID chosen by the framework. The caller is
         *              responsible for avoiding collisions.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when the given BSSID is the all-zero
         *                                  , multicast or broadcast MAC address.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setBssid(@Nullable MacAddress bssid) {
            if (bssid != null) {
                Preconditions.checkArgument(!bssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS));
                if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) {
                    throw new IllegalArgumentException("bssid doesn't support "
                            + "multicast or broadcast mac address");
                }
            }
            mBssid = bssid;
            return this;
        }

        /**
         * Specifies that this AP should use specific security type with the given ASCII passphrase.
         *
         * @param securityType One of the following security types:
         * {@link #SECURITY_TYPE_OPEN},
         * {@link #SECURITY_TYPE_WPA2_PSK},
         * {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
         * {@link #SECURITY_TYPE_WPA3_SAE},
         * {@link #SECURITY_TYPE_WPA3_OWE_TRANSITION},
         * {@link #SECURITY_TYPE_WPA3_OWE}.
         * @param passphrase The passphrase to use for sepcific {@code securityType} configuration
         * or null with {@link #SECURITY_TYPE_OPEN}, {@link #SECURITY_TYPE_WPA3_OWE_TRANSITION},
         * and {@link #SECURITY_TYPE_WPA3_OWE}.
         *
         * @return Builder for chaining.
         * @throws IllegalArgumentException when the passphrase is non-null for
         *             - {@link #SECURITY_TYPE_OPEN}
         *             - {@link #SECURITY_TYPE_WPA3_OWE_TRANSITION}
         *             - {@link #SECURITY_TYPE_WPA3_OWE}
         * @throws IllegalArgumentException when the passphrase is empty for
         *             - {@link #SECURITY_TYPE_WPA2_PSK},
         *             - {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION},
         *             - {@link #SECURITY_TYPE_WPA3_SAE},
         * @throws IllegalArgumentException before {@link android.os.Build.VERSION_CODES#TIRAMISU})
         *         when the passphrase is not between 8 and 63 bytes (inclusive) for
         *             - {@link #SECURITY_TYPE_WPA2_PSK}
         *             - {@link #SECURITY_TYPE_WPA3_SAE_TRANSITION}
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
            if (!SdkLevel.isAtLeastT()
                    && (securityType == SECURITY_TYPE_WPA3_OWE_TRANSITION
                            || securityType == SECURITY_TYPE_WPA3_OWE)) {
                throw new UnsupportedOperationException();
            }
            if (securityType == SECURITY_TYPE_OPEN
                    || securityType == SECURITY_TYPE_WPA3_OWE_TRANSITION
                    || securityType == SECURITY_TYPE_WPA3_OWE) {
                if (passphrase != null) {
                    throw new IllegalArgumentException(
                            "passphrase should be null when security type is open");
                }
            } else {
                Preconditions.checkStringNotEmpty(passphrase);
                if (!SdkLevel.isAtLeastT() && (securityType == SECURITY_TYPE_WPA2_PSK
                        || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION)) {
                    int passphraseByteLength = 0;
                    if (!TextUtils.isEmpty(passphrase)) {
                        passphraseByteLength = passphrase.getBytes(StandardCharsets.UTF_8).length;
                    }
                    if (passphraseByteLength < PSK_MIN_LEN || passphraseByteLength > PSK_MAX_LEN) {
                        throw new IllegalArgumentException(
                                "Passphrase length must be at least " + PSK_MIN_LEN
                                        + " and no more than " + PSK_MAX_LEN
                                        + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode");
                    }
                }
            }
            mSecurityType = securityType;
            mPassphrase = passphrase;
            return this;
        }

        /**
         * Specifies whether the AP is hidden (doesn't broadcast its SSID) or
         * not (broadcasts its SSID).
         * <p>
         * <li>If not set, defaults to false (i.e not a hidden network).</li>
         *
         * @param hiddenSsid true for a hidden SSID, false otherwise.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setHiddenSsid(boolean hiddenSsid) {
            mHiddenSsid = hiddenSsid;
            return this;
        }

        /**
         * Specifies the band for the AP.
         * <p>
         * <li>If not set, defaults to {@link #BAND_2GHZ}.</li>
         *
         * @param band One or combination of the following band type:
         * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when an invalid band type is provided.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setBand(@BandType int band) {
            if (!isBandValid(band)) {
                throw new IllegalArgumentException("Invalid band type: " + band);
            }
            mChannels = new SparseIntArray(1);
            mChannels.put(band, 0);
            return this;
        }

        /**
         * Specifies the bands for the APs.
         * If more than 1 band is set, this will bring up concurrent APs.
         * on the requested bands (if possible).
         * <p>
         *
         * Use {@link WifiManager#isBridgedApConcurrencySupported()} to determine
         * whether or not concurrent APs are supported.
         *
         * Requires the driver to support {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
         * when multiple bands are configured. Otherwise,
         * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will report error code
         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
         *
         * Note: Only supports 2.4GHz + 5GHz bands. If any other band is set, will report error
         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
         *
         * @param bands Array of the {@link #BandType}.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when more than 2 bands are set or an invalid band type
         *                                  is provided.
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @NonNull
        @SystemApi
        public Builder setBands(@NonNull int[] bands) {
            if (!SdkLevel.isAtLeastS()) {
                throw new UnsupportedOperationException();
            }
            if (bands.length == 0 || bands.length > 2) {
                throw new IllegalArgumentException("Unsupported number of bands("
                        + bands.length + ") configured");
            }
            SparseIntArray channels = new SparseIntArray(bands.length);
            for (int val : bands) {
                if (!isBandValid(val)) {
                    throw new IllegalArgumentException("Invalid band type: " + val);
                }
                channels.put(val, 0);
            }
            mChannels = channels;
            return this;
        }


        /**
         * Specifies the channel and associated band for the AP.
         *
         * The channel which AP resides on. Valid channels are country dependent.
         * The {@link SoftApCapability#getSupportedChannelList(int)} can be used to obtain
         * valid channels.
         *
         * <p>
         * If not set, the default for the channel is the special value 0 which has the
         * framework auto-select a valid channel from the band configured with
         * {@link #setBand(int)}.
         *
         * The channel auto selection will be offloaded to driver when
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD}
         * return true. The driver will auto select the best channel (e.g. best performance)
         * based on environment interference. Check {@link SoftApCapability} for more detail.
         *
         * The API contains (band, channel) input since the 6GHz band uses the same channel
         * numbering scheme as is used in the 2.4GHz and 5GHz band. Therefore, both are needed to
         * uniquely identify individual channels.
         *
         * <p>
         * @param channel operating channel of the AP.
         * @param band containing this channel.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when the invalid channel or band type is configured.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setChannel(int channel, @BandType int band) {
            if (!isChannelBandPairValid(channel, band)) {
                throw new IllegalArgumentException("Invalid channel(" + channel
                        + ") & band (" + band + ") configured");
            }
            mChannels = new SparseIntArray(1);
            mChannels.put(band, channel);
            return this;
        }

        /**
         * Specifies the channels and associated bands for the APs.
         * <p>
         * The API contains (band, channel) input since the 6GHz band uses the same channel
         * numbering scheme as is used in the 2.4GHz and 5GHz band. Therefore, both are needed to
         * uniquely identify individual channels.
         * <p>
         * Reference the Wi-Fi channel numbering and the channelization in IEEE 802.11-2016
         * specifications, section 17.3.8.4.2, 17.3.8.4.3 and Table 15-6.
         * <p>
         * Using the special value 0 which has the framework auto-select a valid channel
         * from the band configured.
         * When more than 1 channel/band is set, this will bring up concurrent APs on the requested
         * channels and bands (if possible).
         * Use {@link WifiManager#isBridgedApConcurrencySupported()} to determine
         * whether concurrent APs are supported.
         * If not set, the default value is {@link #BAND_2GHZ} with the special channel value 0
         * which has the framework auto-select from {@link #BAND_2GHZ}.
         *
         * <p>
         * @param channels SparseIntArray (key: {@code #BandType} , value: channel) consists of
         *                 {@code BAND_} and corresponding channel.
         * @return Builder for chaining.
         * @throws IllegalArgumentException when more than 2 channels are set or the invalid
         *                                  channel or band type is configured.
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @NonNull
        public Builder setChannels(@NonNull SparseIntArray channels) {
            if (!SdkLevel.isAtLeastS()) {
                throw new UnsupportedOperationException();
            }
            if (channels.size() == 0 || channels.size() > 2) {
                throw new IllegalArgumentException("Unsupported number of channels("
                        + channels.size() + ") configured");
            }
            for (int i = 0; i < channels.size(); i++) {
                int channel = channels.valueAt(i);
                int band = channels.keyAt(i);
                if (channel == 0) {
                    if (!isBandValid(band)) {
                        throw new IllegalArgumentException("Invalid band type: " + band);
                    }
                } else {
                    if (!isChannelBandPairValid(channel, band)) {
                        throw new IllegalArgumentException("Invalid channel(" + channel
                                + ") & band (" + band + ") configured");
                    }
                }
            }
            mChannels = channels.clone();
            return this;
        }


        /**
         * Specifies the maximum number of clients that can associate to the AP.
         *
         * The maximum number of clients (STAs) which can associate to the AP.
         * The AP will reject association from any clients above this number.
         * Specify a value of 0 to have the framework automatically use the maximum number
         * which the device can support (based on hardware and carrier constraints).
         * <p>
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#getMaxSupportedClients} to get the maximum number of clients
         * which the device supports (based on hardware and carrier constraints).
         *
         * <p>
         * <li>If not set, defaults to 0.</li>
         *
         * This method requires HAL support. If the method is used to set a
         * non-zero {@code maxNumberOfClients} value then
         * {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will report error code
         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
         *
         * <p>
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT} to determine whether
         * or not this feature is supported.
         *
         * @param maxNumberOfClients maximum client number of the AP.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setMaxNumberOfClients(@IntRange(from = 0) int maxNumberOfClients) {
            if (maxNumberOfClients < 0) {
                throw new IllegalArgumentException("maxNumberOfClients should be not negative");
            }
            mMaxNumberOfClients = maxNumberOfClients;
            return this;
        }

        /**
         * Specifies whether auto shutdown is enabled or not.
         * The Soft AP will shut down when there are no devices connected to it for
         * the timeout duration.
         *
         * <p>
         * <li>If not set, defaults to true</li>
         *
         * @param enable true to enable, false to disable.
         * @return Builder for chaining.
         *
         * @see #setShutdownTimeoutMillis(long)
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setAutoShutdownEnabled(boolean enable) {
            mAutoShutdownEnabled = enable;
            return this;
        }

        /**
         * Specifies the shutdown timeout in milliseconds.
         * The Soft AP will shut down when there are no devices connected to it for
         * the timeout duration.
         *
         * Specify a value of {@link #DEFAULT_TIMEOUT} to have the framework automatically use
         * default timeout setting which defined in
         * {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
         *
         * <p>
         * <li>If not set, defaults to {@link #DEFAULT_TIMEOUT}</li>
         * <li>The shut down timeout will apply when {@link #setAutoShutdownEnabled(boolean)} is
         * set to true</li>
         *
         * @param timeoutMillis milliseconds of the timeout delay. Any value less than 1 is invalid
         *                      except {@link #DEFAULT_TIMEOUT}.
         * @return Builder for chaining.
         *
         * @see #setAutoShutdownEnabled(boolean)
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setShutdownTimeoutMillis(@IntRange(from = -1) long timeoutMillis) {
            if (CompatChanges.isChangeEnabled(
                    REMOVE_ZERO_FOR_TIMEOUT_SETTING) && timeoutMillis < 1) {
                if (timeoutMillis != DEFAULT_TIMEOUT) {
                    throw new IllegalArgumentException("Invalid timeout value: " + timeoutMillis);
                }
            } else if (timeoutMillis < 0) {
                throw new IllegalArgumentException("Invalid timeout value from legacy app: "
                        + timeoutMillis);
            }
            mShutdownTimeoutMillis = timeoutMillis;
            return this;
        }

        /**
         * Configure the Soft AP to require manual user control of client association.
         * If disabled (the default) then any client which isn't in the blocked list
         * {@link #getBlockedClientList()} can associate to this Soft AP using the
         * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
         * or user limited - using {@link #setMaxNumberOfClients(int)}).
         *
         * If manual user control is enabled then clients will be accepted, rejected, or require
         * a user approval based on the configuration provided by
         * {@link #setBlockedClientList(List)} and {@link #setAllowedClientList(List)}.
         *
         * <p>
         * This method requires HAL support. HAL support can be determined using
         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
         *
         * <p>
         * If the method is called on a device without HAL support then starting the soft AP
         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
         *
         * <p>
         * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
         *
         * @param enabled true for enabling the control by user, false otherwise.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setClientControlByUserEnabled(boolean enabled) {
            mClientControlByUser = enabled;
            return this;
        }

        /**
         * Configures the set of channel numbers in the specified band that are allowed
         * to be selected by the Automatic Channel Selection (ACS) algorithm.
         * <p>
         *
         * Requires the driver to support {@link SoftApCapability#SOFTAP_FEATURE_ACS_OFFLOAD}.
         * Otherwise, these sets will be ignored.
         * <p>
         *
         * @param band one of the following band types:
         * {@link #BAND_2GHZ}, {@link #BAND_5GHZ}, {@link #BAND_6GHZ}.
         *
         * @param channels that are allowed to be used by ACS algorithm in this band. If it is
         * configured to an empty array or not configured, then all channels within that band
         * will be allowed.
         * <p>
         *
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @SystemApi
        public Builder setAllowedAcsChannels(@BandType int band, @NonNull int[] channels) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }

            if (channels == null) {
                throw new IllegalArgumentException(
                        "Passing a null object to setAllowedAcsChannels");
            }

            if ((band != BAND_2GHZ) && (band != BAND_5GHZ) && (band != BAND_6GHZ)) {
                throw new IllegalArgumentException(
                        "Passing an invalid band to setAllowedAcsChannels");
            }

            for (int channel : channels) {
                if (!isChannelBandPairValid(channel, band)) {
                    throw new IllegalArgumentException(
                            "Invalid channel to setAllowedAcsChannels: band: " + band
                            + "channel: " + channel);
                }
            }

            HashSet<Integer> set = IntStream.of(channels).boxed()
                    .collect(Collectors.toCollection(HashSet::new));
            switch(band) {
                case BAND_2GHZ:
                    mAllowedAcsChannels2g = set;
                    break;
                case BAND_5GHZ:
                    mAllowedAcsChannels5g = set;
                    break;
                case BAND_6GHZ:
                    mAllowedAcsChannels6g = set;
                    break;
            }

            return this;
        }

        /**
         * Sets maximum channel bandwidth for the SoftAp Connection
         *
         * If not set, the SoftAp connection will seek the maximum channel bandwidth achievable on
         * the device. However, in some cases the caller will need to put a cap on the channel
         * bandwidth through this API.
         *
         * @param maxChannelBandwidth one of {@link SoftApInfo#CHANNEL_WIDTH_AUTO},
         * {@link SoftApInfo#CHANNEL_WIDTH_20MHZ}, {@link SoftApInfo#CHANNEL_WIDTH_40MHZ},
         * {@link SoftApInfo#CHANNEL_WIDTH_80MHZ}, {@link SoftApInfo#CHANNEL_WIDTH_160MHZ},
         * or {@link SoftApInfo#CHANNEL_WIDTH_320MHZ}
         *
         * @return builder for chaining
         *
         * @hide
         */
        @NonNull
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @SystemApi
        public Builder setMaxChannelBandwidth(@WifiAnnotations.Bandwidth int maxChannelBandwidth) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }

            switch (maxChannelBandwidth) {
                case SoftApInfo.CHANNEL_WIDTH_AUTO:
                case SoftApInfo.CHANNEL_WIDTH_20MHZ:
                case SoftApInfo.CHANNEL_WIDTH_40MHZ:
                case SoftApInfo.CHANNEL_WIDTH_80MHZ:
                case SoftApInfo.CHANNEL_WIDTH_160MHZ:
                case SoftApInfo.CHANNEL_WIDTH_320MHZ:
                    mMaxChannelBandwidth = maxChannelBandwidth;
                    break;
                default:
                    throw new IllegalArgumentException(
                            "Invalid channel bandwidth value("
                            + maxChannelBandwidth + ")  configured");
            }
            return this;
        }

        /**
         * This method together with {@link setClientControlByUserEnabled(boolean)} control client
         * connections to the AP. If client control by user is disabled using the above method then
         * this API has no effect and clients are allowed to associate to the AP (within limit of
         * max number of clients).
         *
         * If client control by user is enabled then this API configures the list of clients
         * which are explicitly allowed. These are auto-accepted.
         *
         * All other clients which attempt to associate, whose MAC addresses are on neither list,
         * are:
         * <ul>
         * <li>Rejected</li>
         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
         * is issued (which allows the user to add them to the allowed client list if desired).<li>
         * </ul>
         *
         * @param allowedClientList list of clients which are allowed to associate to the AP
         *                          without user pre-approval.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setAllowedClientList(@NonNull List<MacAddress> allowedClientList) {
            mAllowedClientList = new ArrayList<>(allowedClientList);
            return this;
        }

        /**
         * This API configures the list of clients which are blocked and cannot associate
         * to the Soft AP.
         *
         * <p>
         * This method requires HAL support. HAL support can be determined using
         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
         *
         * <p>
         * If the method is called on a device without HAL support then starting the soft AP
         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
         *
         * @param blockedClientList list of clients which are not allowed to associate to the AP.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setBlockedClientList(@NonNull List<MacAddress> blockedClientList) {
            mBlockedClientList = new ArrayList<>(blockedClientList);
            return this;
        }

        /**
         * Specifies the level of MAC randomization for the AP BSSID.
         * The Soft AP BSSID will be randomized only if the BSSID isn't set
         * {@link #setBssid(MacAddress)} and this method is either uncalled
         * or called with {@link #RANDOMIZATION_PERSISTENT} or
         * {@link #RANDOMIZATION_NON_PERSISTENT}. When this method is called with
         * {@link #RANDOMIZATION_PERSISTENT} or {@link #RANDOMIZATION_NON_PERSISTENT}, the caller
         * the caller must not call {@link #setBssid(MacAddress)}.
         *
         * <p>
         * <li>If not set, defaults to {@link #RANDOMIZATION_NON_PERSISTENT}</li>
         *
         * <p>
         * Requires HAL support when set to {@link #RANDOMIZATION_PERSISTENT} or
         * {@link #RANDOMIZATION_NON_PERSISTENT}.
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION} to determine
         * whether or not this feature is supported.
         *
         * @param macRandomizationSetting One of the following setting:
         * {@link #RANDOMIZATION_NONE}, {@link #RANDOMIZATION_PERSISTENT} or
         * {@link #RANDOMIZATION_NON_PERSISTENT}.
         * @return Builder for chaining.
         *
         * @see #setBssid(MacAddress)
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @NonNull
        @SystemApi
        public Builder setMacRandomizationSetting(
                @MacRandomizationSetting int macRandomizationSetting) {
            if (!SdkLevel.isAtLeastS()) {
                throw new UnsupportedOperationException();
            }
            mMacRandomizationSetting = macRandomizationSetting;
            return this;
        }


        /**
         * Specifies whether or not opportunistic shut down of an AP instance in bridged mode
         * is enabled.
         *
         * <p>
         * If enabled, the framework will shutdown one of the AP instances if it is idle for
         * the timeout duration - meaning there are no devices connected to it.
         * If both AP instances are idle for the timeout duration then the framework will
         * shut down the AP instance operating on the higher frequency. For instance,
         * if the AP instances operate at 2.4GHz and 5GHz and are both idle for the
         * timeout duration then the 5GHz AP instance will be shut down.
         * <p>
         *
         * Note: the opportunistic timeout only applies to one AP instance of the bridge AP.
         * If one of the AP instances has already been disabled for any reason, including due to
         * an opportunistic timeout or hardware issues or coexistence issues,
         * then the opportunistic timeout is no longer active.
         *
         * <p>
         * The shutdown timer specified by {@link #setShutdownTimeoutMillis(long)} controls the
         * overall shutdown of the bridged AP and is still in use independently of the opportunistic
         * timer controlled by this AP.
         *
         * <p>
         * <li>If not set, defaults to true</li>
         *
         * @param enable true to enable, false to disable.
         * @return Builder for chaining.
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @NonNull
        @SystemApi
        public Builder setBridgedModeOpportunisticShutdownEnabled(boolean enable) {
            if (!SdkLevel.isAtLeastS()) {
                throw new UnsupportedOperationException();
            }
            mBridgedModeOpportunisticShutdownEnabled = enable;
            return this;
        }

        /**
         * Specifies whether or not to enable 802.11ax on the Soft AP.
         *
         * <p>
         * Note: Only relevant when the device supports 802.11ax on the Soft AP.
         * If enabled on devices that do not support 802.11ax then ignored.
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX} to determine
         * whether or not 802.11ax is supported on the Soft AP.
         * <p>
         * <li>If not set, defaults to true - which will be ignored on devices
         * which do not support 802.11ax</li>
         *
         * @param enable true to enable, false to disable.
         * @return Builder for chaining.
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.S)
        @NonNull
        @SystemApi
        public Builder setIeee80211axEnabled(boolean enable) {
            if (!SdkLevel.isAtLeastS()) {
                throw new UnsupportedOperationException();
            }
            mIeee80211axEnabled = enable;
            return this;
        }

        /**
         * Specifies whether or not to enable 802.11be on the Soft AP.
         *
         * <p>
         * Note: Only relevant when the device supports 802.11be on the Soft AP.
         * If enabled on devices that do not support 802.11be then ignored.
         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
         * {@link SoftApCapability#areFeaturesSupported(long)}
         * with {@link SoftApCapability.SOFTAP_FEATURE_IEEE80211_BE} to determine
         * whether or not 802.11be is supported on the Soft AP.
         * <p>
         * <li>If not set, defaults to true - which will be ignored on devices
         * which do not support 802.11be</li>
         *
         * @param enable true to enable, false to disable.
         * @return Builder for chaining.
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @NonNull
        @SystemApi
        public Builder setIeee80211beEnabled(boolean enable) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }
            mIeee80211beEnabled = enable;
            return this;
        }

        /**
         * Specifies whether or not the configuration is configured by user.
         *
         * @param isUserConfigured true to user configuration, false otherwise.
         * @return Builder for chaining.
         *
         * @hide
         */
        @NonNull
        public Builder setUserConfiguration(boolean isUserConfigured) {
            mIsUserConfiguration = isUserConfigured;
            return this;
        }

        /**
         * Specifies bridged mode opportunistic shutdown timeout in milliseconds.
         * An instance of bridged Soft AP will shut down when there is no device connected to it
         * for this timeout duration.
         *
         * Specify a value of {@link DEFAULT_TIMEOUT} to have the framework automatically use
         * default timeout setting defined by
         * {@link
         * R.integer.config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond}
         *
         * <p>
         * <li>If not set, defaults to {@link #DEFAULT_TIMEOUT}</li>
         * <li>The shut down timeout will apply when
         * {@link #setBridgedModeOpportunisticShutdownEnabled(boolean)} is set to true</li>
         *
         * @param timeoutMillis milliseconds of the timeout delay. Any value less than 1 is invalid
         *                      except {@link #DEFAULT_TIMEOUT}.
         * @return Builder for chaining.
         *
         * @see #setBridgedModeOpportunisticShutdownEnabled(boolean)
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
        @NonNull
        @SystemApi
        public Builder setBridgedModeOpportunisticShutdownTimeoutMillis(
                @IntRange(from = -1) long timeoutMillis) {
            if (!SdkLevel.isAtLeastT()) {
                throw new UnsupportedOperationException();
            }
            if (timeoutMillis < 1 && timeoutMillis != DEFAULT_TIMEOUT) {
                throw new IllegalArgumentException("Invalid timeout value: " + timeoutMillis);
            }
            mBridgedModeOpportunisticShutdownTimeoutMillis = timeoutMillis;
            return this;
        }

        /**
         * @param mac persistent randomized MacAddress generated by the frameworks.
         * @hide
         */
        @NonNull
        public Builder setRandomizedMacAddress(@NonNull MacAddress mac) {
            if (mac == null) {
                throw new IllegalArgumentException("setRandomizedMacAddress received"
                        + " null MacAddress.");
            }
            mPersistentRandomizedMacAddress = mac;
            return this;
        }

        /**
         * Set additional vendor-provided configuration data.
         *
         * @param vendorData List of {@link OuiKeyedData} containing the vendor-provided
         *     configuration data. Note that multiple elements with the same OUI are allowed.
         * @return Builder for chaining.
         *
         * @hide
         */
        @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
        @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
        @NonNull
        @SystemApi
        public Builder setVendorData(@NonNull List<OuiKeyedData> vendorData) {
            if (!SdkLevel.isAtLeastV()) {
                throw new UnsupportedOperationException();
            }
            if (vendorData == null) {
                throw new IllegalArgumentException("setVendorData received a null value");
            }
            mVendorData = vendorData;
            return this;
        }

        /**
         * Specifies whether or not client isolation is enabled.
         *
         * Client isolation can be used to disallow a connected Soft AP
         * client to communicate with other connected clients.
         *
         * @param isClientIsolationEnabled true when enabling client isolation.
         * @return Builder for chaining.
         *
         * @hide
         */
        @FlaggedApi(Flags.FLAG_AP_ISOLATE)
        @RequiresApi(Build.VERSION_CODES.BAKLAVA)
        @NonNull
        @SystemApi
        public Builder setClientIsolationEnabled(boolean isClientIsolationEnabled) {
            if (!Environment.isSdkAtLeastB()) {
                throw new UnsupportedOperationException();
            }
            mIsClientIsolationEnabled = isClientIsolationEnabled;
            return this;
        }
    }
}
