/*
 * Copyright (C) 2018 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 static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.net.MacAddress;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * The Network Suggestion object is used to provide a Wi-Fi network for consideration when
 * auto-connecting to networks. Apps cannot directly create this object, they must use
 * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object.
 *<p>
 * Apps can provide a list of such networks to the platform using
 * {@link WifiManager#addNetworkSuggestions(List)}.
 */
public final class WifiNetworkSuggestion implements Parcelable {
    /**
     * Builder used to create {@link WifiNetworkSuggestion} objects.
     */
    public static final class Builder {
        private static final int UNASSIGNED_PRIORITY = -1;

        /**
         * SSID of the network.
         */
        private String mSsid;
        /**
         * Optional BSSID within the network.
         */
        private MacAddress mBssid;
        /**
         * Whether this is an OWE network or not.
         */
        private boolean mIsEnhancedOpen;
        /**
         * Pre-shared key for use with WPA-PSK networks.
         */
        private @Nullable String mWpa2PskPassphrase;
        /**
         * Pre-shared key for use with WPA3-SAE networks.
         */
        private @Nullable String mWpa3SaePassphrase;
        /**
         * The enterprise configuration details specifying the EAP method,
         * certificates and other settings associated with the WPA/WPA2-Enterprise networks.
         */
        private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
        /**
         * The enterprise configuration details specifying the EAP method,
         * certificates and other settings associated with the WPA3-Enterprise networks.
         */
        private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
        /**
         * The passpoint config for use with Hotspot 2.0 network
         */
        private @Nullable PasspointConfiguration mPasspointConfiguration;
        /**
         * This is a network that does not broadcast its SSID, so an
         * SSID-specific probe request must be used for scans.
         */
        private boolean mIsHiddenSSID;
        /**
         * Whether app needs to log in to captive portal to obtain Internet access.
         */
        private boolean mIsAppInteractionRequired;
        /**
         * Whether user needs to log in to captive portal to obtain Internet access.
         */
        private boolean mIsUserInteractionRequired;
        /**
         * Whether this network is metered or not.
         */
        private int mMeteredOverride;
        /**
         * Priority of this network among other network suggestions provided by the app.
         * The lower the number, the higher the priority (i.e value of 0 = highest priority).
         */
        private int mPriority;

        /**
         * The carrier ID identifies the operator who provides this network configuration.
         *    see {@link TelephonyManager#getSimCarrierId()}
         */
        private int mCarrierId;

        /**
         * Whether this network is shared credential with user to allow user manually connect.
         */
        private boolean mIsSharedWithUser;

        /**
         * Whether the setCredentialSharedWithUser have been called.
         */
        private boolean mIsSharedWithUserSet;

        /**
         * Whether this network is initialized with auto-join enabled (the default) or not.
         */
        private boolean mIsInitialAutojoinEnabled;

        /**
         * Pre-shared key for use with WAPI-PSK networks.
         */
        private @Nullable String mWapiPskPassphrase;

        /**
         * The enterprise configuration details specifying the EAP method,
         * certificates and other settings associated with the WAPI networks.
         */
        private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig;

        /**
         * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
         */
        private boolean mIsNetworkUntrusted;

        public Builder() {
            mSsid = null;
            mBssid =  null;
            mIsEnhancedOpen = false;
            mWpa2PskPassphrase = null;
            mWpa3SaePassphrase = null;
            mWpa2EnterpriseConfig = null;
            mWpa3EnterpriseConfig = null;
            mPasspointConfiguration = null;
            mIsHiddenSSID = false;
            mIsAppInteractionRequired = false;
            mIsUserInteractionRequired = false;
            mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE;
            mIsSharedWithUser = true;
            mIsSharedWithUserSet = false;
            mIsInitialAutojoinEnabled = true;
            mPriority = UNASSIGNED_PRIORITY;
            mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
            mWapiPskPassphrase = null;
            mWapiEnterpriseConfig = null;
            mIsNetworkUntrusted = false;
        }

        /**
         * Set the unicode SSID for the network.
         * <p>
         * <li>Overrides any previous value set using {@link #setSsid(String)}.</li>
         *
         * @param ssid The SSID of the network. It must be valid Unicode.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if the SSID is not valid unicode.
         */
        public @NonNull Builder setSsid(@NonNull String ssid) {
            checkNotNull(ssid);
            final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder();
            if (!unicodeEncoder.canEncode(ssid)) {
                throw new IllegalArgumentException("SSID is not a valid unicode string");
            }
            mSsid = new String(ssid);
            return this;
        }

        /**
         * Set the BSSID to use for filtering networks from scan results. Will only match network
         * whose BSSID is identical to the specified value.
         * <p>
         * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID
         * with the specified SSID will be considered for connection.
         * <li>If set, only the specified BSSID with the specified SSID will be considered for
         * connection.</li>
         * <li>If not set, all BSSIDs with the specified SSID will be considered for connection.
         * </li>
         * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li>
         *
         * @param bssid BSSID of the network.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setBssid(@NonNull MacAddress bssid) {
            checkNotNull(bssid);
            mBssid = MacAddress.fromBytes(bssid.toByteArray());
            return this;
        }

        /**
         * Specifies whether this represents an Enhanced Open (OWE) network.
         *
         * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open,
         *                       {@code false} otherwise.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) {
            mIsEnhancedOpen = isEnhancedOpen;
            return this;
        }

        /**
         * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to
         * WPA2-PSK networks.
         *
         * @param passphrase passphrase of the network.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
         */
        public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) {
            checkNotNull(passphrase);
            final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
            if (!asciiEncoder.canEncode(passphrase)) {
                throw new IllegalArgumentException("passphrase not ASCII encodable");
            }
            mWpa2PskPassphrase = passphrase;
            return this;
        }

        /**
         * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE
         * networks.
         *
         * @param passphrase passphrase of the network.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
         */
        public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) {
            checkNotNull(passphrase);
            final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
            if (!asciiEncoder.canEncode(passphrase)) {
                throw new IllegalArgumentException("passphrase not ASCII encodable");
            }
            mWpa3SaePassphrase = passphrase;
            return this;
        }

        /**
         * Set the associated enterprise configuration for this network. Needed for authenticating
         * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description.
         *
         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if configuration CA certificate or
         *                                  AltSubjectMatch/DomainSuffixMatch is not set.
         */
        public @NonNull Builder setWpa2EnterpriseConfig(
                @NonNull WifiEnterpriseConfig enterpriseConfig) {
            checkNotNull(enterpriseConfig);
            if (enterpriseConfig.isInsecure()) {
                throw new IllegalArgumentException("Enterprise configuration is insecure");
            }
            mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
            return this;
        }

        /**
         * Set the associated enterprise configuration for this network. Needed for authenticating
         * to WPA3-Enterprise networks (standard and 192-bit security). See
         * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the
         * client and CA certificates must be provided, and must be of type of either
         * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
         * (OID 1.2.840.10045.4.3.3).
         *
         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if configuration CA certificate or
         *                                  AltSubjectMatch/DomainSuffixMatch is not set.
         */
        public @NonNull Builder setWpa3EnterpriseConfig(
                @NonNull WifiEnterpriseConfig enterpriseConfig) {
            checkNotNull(enterpriseConfig);
            if (enterpriseConfig.isInsecure()) {
                throw new IllegalArgumentException("Enterprise configuration is insecure");
            }
            mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
            return this;
        }

        /**
         * Set the associated Passpoint configuration for this network. Needed for authenticating
         * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description.
         *
         * @param passpointConfig Instance of {@link PasspointConfiguration}.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if passpoint configuration is invalid.
         */
        public @NonNull Builder setPasspointConfig(
                @NonNull PasspointConfiguration passpointConfig) {
            checkNotNull(passpointConfig);
            if (!passpointConfig.validate()) {
                throw new IllegalArgumentException("Passpoint configuration is invalid");
            }
            mPasspointConfiguration = passpointConfig;
            return this;
        }

        /**
         * Set the carrier ID of the network operator. The carrier ID associates a Suggested
         * network with a specific carrier (and therefore SIM). The carrier ID must be provided
         * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA,
         * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication.
         * @param carrierId see {@link TelephonyManager#getSimCarrierId()}.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         *
         * @hide
         */
        @SystemApi
        @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
        public @NonNull Builder setCarrierId(int carrierId) {
            mCarrierId = carrierId;
            return this;
        }

        /**
         * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
         * WAPI-PSK networks.
         *
         * @param passphrase passphrase of the network.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
         *
         */
        public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) {
            checkNotNull(passphrase);
            final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
            if (!asciiEncoder.canEncode(passphrase)) {
                throw new IllegalArgumentException("passphrase not ASCII encodable");
            }
            mWapiPskPassphrase = passphrase;
            return this;
        }

        /**
         * Set the associated enterprise configuration for this network. Needed for authenticating
         * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description.
         *
         * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setWapiEnterpriseConfig(
                @NonNull WifiEnterpriseConfig enterpriseConfig) {
            checkNotNull(enterpriseConfig);
            mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
            return this;
        }

        /**
         * Specifies whether this represents a hidden network.
         * <p>
         * <li>If not set, defaults to false (i.e not a hidden network).</li>
         *
         * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
         *                     otherwise.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
            mIsHiddenSSID = isHiddenSsid;
            return this;
        }

        /**
         * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
         * <p>
         * This will dictate if the directed broadcast
         * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the
         * app after successfully connecting to the network.
         * Use this for captive portal type networks where the app needs to authenticate the user
         * before the device can access the network.
         * <p>
         * <li>If not set, defaults to false (i.e no app interaction required).</li>
         *
         * @param isAppInteractionRequired {@code true} to indicate that app interaction is
         *                                 required, {@code false} otherwise.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) {
            mIsAppInteractionRequired = isAppInteractionRequired;
            return this;
        }

        /**
         * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
         * <p>
         * <li>If not set, defaults to false (i.e no user interaction required).</li>
         *
         * @param isUserInteractionRequired {@code true} to indicate that user interaction is
         *                                  required, {@code false} otherwise.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) {
            mIsUserInteractionRequired = isUserInteractionRequired;
            return this;
        }

        /**
         * Specify the priority of this network among other network suggestions provided by the same
         * app (priorities have no impact on suggestions by different apps). The higher the number,
         * the higher the priority (i.e value of 0 = lowest priority).
         * <p>
         * <li>If not set, defaults a lower priority than any assigned priority.</li>
         *
         * @param priority Integer number representing the priority among suggestions by the app.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         * @throws IllegalArgumentException if the priority value is negative.
         */
        public @NonNull Builder setPriority(@IntRange(from = 0) int priority) {
            if (priority < 0) {
                throw new IllegalArgumentException("Invalid priority value " + priority);
            }
            mPriority = priority;
            return this;
        }

        /**
         * Specifies whether this network is metered.
         * <p>
         * <li>If not set, defaults to detect automatically.</li>
         *
         * @param isMetered {@code true} to indicate that the network is metered, {@code false}
         *                  for not metered.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsMetered(boolean isMetered) {
            if (isMetered) {
                mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
            } else {
                mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
            }
            return this;
        }

        /**
         * Specifies whether the network credentials provided with this suggestion can be used by
         * the user to explicitly (manually) connect to this network. If true this network will
         * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect
         * to it with the provided credentials. If false, the user will need to enter network
         * credentials and the resulting configuration will become a user saved network.
         * <p>
         * <li>Note: Only valid for secure (non-open) networks.
         * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
         * networks and false for open networks.</li>
         *
         * @param isShared {@code true} to indicate that the credentials may be used by the user to
         *                              manually connect to the network, {@code false} otherwise.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
            mIsSharedWithUser = isShared;
            mIsSharedWithUserSet = true;
            return this;
        }

        /**
         * Specifies whether the suggestion is created with auto-join enabled or disabled. The
         * user may modify the auto-join configuration of a suggestion directly once the device
         * associates to the network.
         * <p>
         * If auto-join is initialized as disabled the user may still be able to manually connect
         * to the network. Therefore, disabling auto-join only makes sense if
         * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
         * itself implies a secure (non-open) network.
         * <p>
         * If not set, defaults to true (i.e. auto-join is initialized as enabled).
         *
         * @param enabled true for initializing with auto-join enabled (the default), false to
         *                initializing with auto-join disabled.
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) {
            mIsInitialAutojoinEnabled = enabled;
            return this;
        }

        /**
         * Specifies whether the system will bring up the network (if selected) as untrusted. An
         * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
         * capability removed. The Wi-Fi network selection process may use this information to
         * influence priority of the suggested network for Wi-Fi network selection (most likely to
         * reduce it). The connectivity service may use this information to influence the overall
         * network configuration of the device.
         * <p>
         * <li> An untrusted network's credentials may not be shared with the user using
         * {@link #setCredentialSharedWithUser(boolean)}.</li>
         * <li> If not set, defaults to false (i.e. network is trusted).</li>
         *
         * @param isUntrusted Boolean indicating whether the network should be brought up untrusted
         *                    (if true) or trusted (if false).
         * @return Instance of {@link Builder} to enable chaining of the builder method.
         */
        public @NonNull Builder setUntrusted(boolean isUntrusted) {
            mIsNetworkUntrusted = isUntrusted;
            return this;
        }

        private void setSecurityParamsInWifiConfiguration(
                @NonNull WifiConfiguration configuration) {
            if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
                // WifiConfiguration.preSharedKey needs quotes around ASCII password.
                configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
            } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
                // WifiConfiguration.preSharedKey needs quotes around ASCII password.
                configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
            } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                configuration.enterpriseConfig = mWpa2EnterpriseConfig;
            } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
                if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
                        && WifiEnterpriseConfig.isSuiteBCipherCert(
                        mWpa3EnterpriseConfig.getClientCertificate())
                        && WifiEnterpriseConfig.isSuiteBCipherCert(
                        mWpa3EnterpriseConfig.getCaCertificate())) {
                    // WPA3-Enterprise in 192-bit security mode (Suite-B)
                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
                } else {
                    // WPA3-Enterprise
                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
                    configuration.allowedPairwiseCiphers.set(
                            WifiConfiguration.PairwiseCipher.GCMP_256);
                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                    configuration.requirePmf = true;
                }
                configuration.enterpriseConfig = mWpa3EnterpriseConfig;
            } else if (mIsEnhancedOpen) { // OWE network
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
            } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network.
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
                // WifiConfiguration.preSharedKey needs quotes around ASCII password.
                configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\"";
            } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
                configuration.enterpriseConfig = mWapiEnterpriseConfig;
            } else { // Open network
                configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
            }
        }

        /**
         * Helper method to build WifiConfiguration object from the builder.
         * @return Instance of {@link WifiConfiguration}.
         */
        private WifiConfiguration buildWifiConfiguration() {
            final WifiConfiguration wifiConfiguration = new WifiConfiguration();
            // WifiConfiguration.SSID needs quotes around unicode SSID.
            wifiConfiguration.SSID = "\"" + mSsid + "\"";
            if (mBssid != null) {
                wifiConfiguration.BSSID = mBssid.toString();
            }

            setSecurityParamsInWifiConfiguration(wifiConfiguration);

            wifiConfiguration.hiddenSSID = mIsHiddenSSID;
            wifiConfiguration.priority = mPriority;
            wifiConfiguration.meteredOverride = mMeteredOverride;
            wifiConfiguration.carrierId = mCarrierId;
            wifiConfiguration.trusted = !mIsNetworkUntrusted;
            return wifiConfiguration;
        }

        private void validateSecurityParams() {
            int numSecurityTypes = 0;
            numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
            numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
            numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
            numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0;
            numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
            numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
            numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0;
            numSecurityTypes += mPasspointConfiguration != null ? 1 : 0;
            if (numSecurityTypes > 1) {
                throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
                        + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
                        + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto"
                        + " or setPasspointConfig can be invoked for network suggestion");
            }
        }

        private WifiConfiguration buildWifiConfigurationForPasspoint() {
            WifiConfiguration wifiConfiguration = new WifiConfiguration();
            wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn();
            wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId());
            wifiConfiguration.priority = mPriority;
            wifiConfiguration.meteredOverride = mMeteredOverride;
            wifiConfiguration.trusted = !mIsNetworkUntrusted;
            mPasspointConfiguration.setCarrierId(mCarrierId);
            mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
            return wifiConfiguration;
        }

        /**
         * Create a network suggestion object for use in
         * {@link WifiManager#addNetworkSuggestions(List)}.
         *
         *<p class="note">
         * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
         * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
         * the platform.
         * </p>
         *
         * For example:
         * To provide credentials for one open, one WPA2, one WPA3 network with their
         * corresponding SSID's and one with Passpoint config:
         *
         * <pre>{@code
         * final WifiNetworkSuggestion suggestion1 =
         *      new Builder()
         *      .setSsid("test111111")
         *      .build();
         * final WifiNetworkSuggestion suggestion2 =
         *      new Builder()
         *      .setSsid("test222222")
         *      .setWpa2Passphrase("test123456")
         *      .build();
         * final WifiNetworkSuggestion suggestion3 =
         *      new Builder()
         *      .setSsid("test333333")
         *      .setWpa3Passphrase("test6789")
         *      .build();
         * final PasspointConfiguration passpointConfig= new PasspointConfiguration();
         * // configure passpointConfig to include a valid Passpoint configuration
         * final WifiNetworkSuggestion suggestion4 =
         *      new Builder()
         *      .setPasspointConfig(passpointConfig)
         *      .build();
         * final List<WifiNetworkSuggestion> suggestionsList =
         *      new ArrayList<WifiNetworkSuggestion> { {
         *          add(suggestion1);
         *          add(suggestion2);
         *          add(suggestion3);
         *          add(suggestion4);
         *      } };
         * final WifiManager wifiManager =
         *      context.getSystemService(Context.WIFI_SERVICE);
         * wifiManager.addNetworkSuggestions(suggestionsList);
         * // ...
         * }</pre>
         *
         * @return Instance of {@link WifiNetworkSuggestion}
         * @throws IllegalStateException on invalid params set
         * @see WifiNetworkSuggestion
         */
        public @NonNull WifiNetworkSuggestion build() {
            validateSecurityParams();
            WifiConfiguration wifiConfiguration;
            if (mPasspointConfiguration != null) {
                if (mSsid != null) {
                    throw new IllegalStateException("setSsid should not be invoked for suggestion "
                            + "with Passpoint configuration");
                }
                if (mIsHiddenSSID) {
                    throw new IllegalStateException("setIsHiddenSsid should not be invoked for "
                            + "suggestion with Passpoint configuration");
                }
                wifiConfiguration = buildWifiConfigurationForPasspoint();
            } else {
                if (mSsid == null) {
                    throw new IllegalStateException("setSsid should be invoked for suggestion");
                }
                if (TextUtils.isEmpty(mSsid)) {
                    throw new IllegalStateException("invalid ssid for suggestion");
                }
                if (mBssid != null
                        && (mBssid.equals(MacAddress.BROADCAST_ADDRESS)
                        || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) {
                    throw new IllegalStateException("invalid bssid for suggestion");
                }
                wifiConfiguration = buildWifiConfiguration();
                if (wifiConfiguration.isOpenNetwork()) {
                    if (mIsSharedWithUserSet && mIsSharedWithUser) {
                        throw new IllegalStateException("Open network should not be "
                                + "setCredentialSharedWithUser to true");
                    }
                    mIsSharedWithUser = false;
                }
            }
            if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) {
                throw new IllegalStateException("Should have not a network with both "
                        + "setCredentialSharedWithUser and "
                        + "setIsAutojoinEnabled set to false");
            }
            if (mIsNetworkUntrusted) {
                if (mIsSharedWithUserSet && mIsSharedWithUser) {
                    throw new IllegalStateException("Should not be both"
                            + "setCredentialSharedWithUser and +"
                            + "setIsNetworkAsUntrusted to true");
                }
                mIsSharedWithUser = false;
            }
            return new WifiNetworkSuggestion(
                    wifiConfiguration,
                    mPasspointConfiguration,
                    mIsAppInteractionRequired,
                    mIsUserInteractionRequired,
                    mIsSharedWithUser,
                    mIsInitialAutojoinEnabled);
        }
    }

    /**
     * Network configuration for the provided network.
     * @hide
     */
    @NonNull
    public final WifiConfiguration wifiConfiguration;

    /**
     * Passpoint configuration for the provided network.
     * @hide
     */
    @Nullable
    public final PasspointConfiguration passpointConfiguration;

    /**
     * Whether app needs to log in to captive portal to obtain Internet access.
     * @hide
     */
    public final boolean isAppInteractionRequired;

    /**
     * Whether user needs to log in to captive portal to obtain Internet access.
     * @hide
     */
    public final boolean isUserInteractionRequired;

    /**
     * Whether app share credential with the user, allow user use provided credential to
     * connect network manually.
     * @hide
     */
    public final boolean isUserAllowedToManuallyConnect;

    /**
     * Whether the suggestion will be initialized as auto-joined or not.
     * @hide
     */
    public final boolean isInitialAutoJoinEnabled;

    /** @hide */
    public WifiNetworkSuggestion() {
        this.wifiConfiguration = new WifiConfiguration();
        this.passpointConfiguration = null;
        this.isAppInteractionRequired = false;
        this.isUserInteractionRequired = false;
        this.isUserAllowedToManuallyConnect = true;
        this.isInitialAutoJoinEnabled = true;
    }

    /** @hide */
    public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration,
                                 @Nullable PasspointConfiguration passpointConfiguration,
                                 boolean isAppInteractionRequired,
                                 boolean isUserInteractionRequired,
                                 boolean isUserAllowedToManuallyConnect,
                                 boolean isInitialAutoJoinEnabled) {
        checkNotNull(networkConfiguration);
        this.wifiConfiguration = networkConfiguration;
        this.passpointConfiguration = passpointConfiguration;

        this.isAppInteractionRequired = isAppInteractionRequired;
        this.isUserInteractionRequired = isUserInteractionRequired;
        this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
        this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
    }

    public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
            new Creator<WifiNetworkSuggestion>() {
                @Override
                public WifiNetworkSuggestion createFromParcel(Parcel in) {
                    return new WifiNetworkSuggestion(
                            in.readParcelable(null), // wifiConfiguration
                            in.readParcelable(null), // PasspointConfiguration
                            in.readBoolean(), // isAppInteractionRequired
                            in.readBoolean(), // isUserInteractionRequired
                            in.readBoolean(), // isSharedCredentialWithUser
                            in.readBoolean()  // isAutojoinEnabled
                    );
                }

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(wifiConfiguration, flags);
        dest.writeParcelable(passpointConfiguration, flags);
        dest.writeBoolean(isAppInteractionRequired);
        dest.writeBoolean(isUserInteractionRequired);
        dest.writeBoolean(isUserAllowedToManuallyConnect);
        dest.writeBoolean(isInitialAutoJoinEnabled);
    }

    @Override
    public int hashCode() {
        return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
                wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
    }

    /**
     * Equals for network suggestions.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof WifiNetworkSuggestion)) {
            return false;
        }
        WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
        if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) {
            return false;
        }

        return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
                && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
                && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                lhs.wifiConfiguration.allowedKeyManagement)
                && TextUtils.equals(this.wifiConfiguration.getKey(),
                lhs.wifiConfiguration.getKey());
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ")
                .append("SSID=").append(wifiConfiguration.SSID)
                .append(", BSSID=").append(wifiConfiguration.BSSID)
                .append(", FQDN=").append(wifiConfiguration.FQDN)
                .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
                .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
                .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
                .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
                .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
                .append(" ]");
        return sb.toString();
    }

    /**
     * Get the {@link WifiConfiguration} associated with this Suggestion.
     * @hide
     */
    @SystemApi
    @NonNull
    public WifiConfiguration getWifiConfiguration() {
        return wifiConfiguration;
    }

    /**
     * Get the BSSID, or null if unset.
     * @see Builder#setBssid(MacAddress)
     */
    @Nullable
    public MacAddress getBssid() {
        if (wifiConfiguration.BSSID == null) {
            return null;
        }
        return MacAddress.fromString(wifiConfiguration.BSSID);
    }

    /** @see Builder#setCredentialSharedWithUser(boolean) */
    public boolean isCredentialSharedWithUser() {
        return isUserAllowedToManuallyConnect;
    }

    /** @see Builder#setIsAppInteractionRequired(boolean) */
    public boolean isAppInteractionRequired() {
        return isAppInteractionRequired;
    }

    /** @see Builder#setIsEnhancedOpen(boolean)  */
    public boolean isEnhancedOpen() {
        return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
    }

    /** @see Builder#setIsHiddenSsid(boolean)  */
    public boolean isHiddenSsid() {
        return wifiConfiguration.hiddenSSID;
    }

    /** @see Builder#setIsInitialAutojoinEnabled(boolean)  */
    public boolean isInitialAutojoinEnabled() {
        return isInitialAutoJoinEnabled;
    }

    /** @see Builder#setIsMetered(boolean)  */
    public boolean isMetered() {
        return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
    }

    /** @see Builder#setIsUserInteractionRequired(boolean)  */
    public boolean isUserInteractionRequired() {
        return isUserInteractionRequired;
    }

    /**
     * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this
     * Suggestion is not for a Passpoint network.
     */
    @Nullable
    public PasspointConfiguration getPasspointConfig() {
        return passpointConfiguration;
    }

    /** @see Builder#setPriority(int)  */
    @IntRange(from = 0)
    public int getPriority() {
        return wifiConfiguration.priority;
    }

    /**
     * Return the SSID of the network, or null if this is a Passpoint network.
     * @see Builder#setSsid(String)
     */
    @Nullable
    public String getSsid() {
        if (wifiConfiguration.SSID == null) {
            return null;
        }
        return WifiInfo.sanitizeSsid(wifiConfiguration.SSID);
    }

    /** @see Builder#setUntrusted(boolean)  */
    public boolean isUntrusted() {
        return !wifiConfiguration.trusted;
    }

    /**
     * Get the WifiEnterpriseConfig, or null if unset.
     * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
     * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
     * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)
     */
    @Nullable
    public WifiEnterpriseConfig getEnterpriseConfig() {
        if (!wifiConfiguration.isEnterprise()) {
            return null;
        }
        return wifiConfiguration.enterpriseConfig;
    }

    /**
     * Get the passphrase, or null if unset.
     * @see Builder#setWapiPassphrase(String)
     * @see Builder#setWpa2Passphrase(String)
     * @see Builder#setWpa3Passphrase(String)
     */
    @Nullable
    public String getPassphrase() {
        if (wifiConfiguration.preSharedKey == null) {
            return null;
        }
        return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
    }
}
