content: /*
   * Copyright (C) 2008 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.IntDef;
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.annotation.UnsupportedAppUsage;
  import android.content.pm.PackageManager;
  import android.net.IpConfiguration;
  import android.net.IpConfiguration.ProxySettings;
  import android.net.MacAddress;
  import android.net.NetworkSpecifier;
  import android.net.ProxyInfo;
  import android.net.StaticIpConfiguration;
  import android.net.Uri;
  import android.os.Build;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.text.TextUtils;
  import android.util.BackupUtils;
  import android.util.Log;
  import android.util.TimeUtils;
  
  import java.io.ByteArrayOutputStream;
  import java.io.DataInputStream;
  import java.io.DataOutputStream;
  import java.io.IOException;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.Arrays;
  import java.util.BitSet;
  import java.util.HashMap;
  
  /**
   * A class representing a configured Wi-Fi network, including the
   * security configuration.
   *
   * @deprecated Use {@link WifiNetworkSpecifier.Builder} to create {@link NetworkSpecifier} and
   * {@link WifiNetworkSuggestion.Builder} to create {@link WifiNetworkSuggestion}. This will become a
   * system use only object in the future.
   */
  @Deprecated
  public class WifiConfiguration implements Parcelable {
      private static final String TAG = "WifiConfiguration";
      /**
       * Current Version of the Backup Serializer.
      */
      private static final int BACKUP_VERSION = 3;
      /** {@hide} */
      public static final String ssidVarName = "ssid";
      /** {@hide} */
      public static final String bssidVarName = "bssid";
      /** {@hide} */
      public static final String pskVarName = "psk";
      /** {@hide} */
      @Deprecated
      @UnsupportedAppUsage
      public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" };
      /** {@hide} */
      @Deprecated
      public static final String wepTxKeyIdxVarName = "wep_tx_keyidx";
      /** {@hide} */
      public static final String priorityVarName = "priority";
      /** {@hide} */
      public static final String hiddenSSIDVarName = "scan_ssid";
      /** {@hide} */
      public static final String pmfVarName = "ieee80211w";
      /** {@hide} */
      public static final String updateIdentiferVarName = "update_identifier";
      /** {@hide} */
      public static final int INVALID_NETWORK_ID = -1;
      /** {@hide} */
      public static final int LOCAL_ONLY_NETWORK_ID = -2;
  
      /** {@hide} */
      private String mPasspointManagementObjectTree;
      /** {@hide} */
      private static final int MAXIMUM_RANDOM_MAC_GENERATION_RETRY = 3;
  
      /**
       * Recognized key management schemes.
       */
      public static class KeyMgmt {
          private KeyMgmt() { }
  
          /** WPA is not used; plaintext or static WEP could be used. */
          public static final int NONE = 0;
          /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
          public static final int WPA_PSK = 1;
          /** WPA using EAP authentication. Generally used with an external authentication server. */
          public static final int WPA_EAP = 2;
          /** IEEE 802.1X using EAP authentication and (optionally) dynamically
           * generated WEP keys. */
          public static final int IEEE8021X = 3;
  
          /** WPA2 pre-shared key for use with soft access point
            * (requires {@code preSharedKey} to be specified).
            * @hide
            */
          @SystemApi
          public static final int WPA2_PSK = 4;
          /**
           * Hotspot 2.0 r2 OSEN:
           * @hide
           */
          public static final int OSEN = 5;
  
          /**
           * IEEE 802.11r Fast BSS Transition with PSK authentication.
           * @hide
           */
          public static final int FT_PSK = 6;
  
          /**
           * IEEE 802.11r Fast BSS Transition with EAP authentication.
           * @hide
           */
          public static final int FT_EAP = 7;
  
          /**
           * Simultaneous Authentication of Equals
           */
          public static final int SAE = 8;
  
          /**
           * Opportunististic Wireless Encryption
           */
          public static final int OWE = 9;
  
          /**
           * SUITE_B_192 192 bit level
           */
          public static final int SUITE_B_192 = 10;
  
          /**
           * WPA pre-shared key with stronger SHA256-based algorithms.
           * @hide
           */
          public static final int WPA_PSK_SHA256 = 11;
  
          /**
           * WPA using EAP authentication with stronger SHA256-based algorithms.
           * @hide
           */
          public static final int WPA_EAP_SHA256 = 12;
  
          public static final String varName = "key_mgmt";
  
          public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP",
                  "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP",
                  "SAE", "OWE", "SUITE_B_192", "WPA_PSK_SHA256", "WPA_EAP_SHA256" };
      }
  
      /**
       * Recognized security protocols.
       */
      public static class Protocol {
          private Protocol() { }
  
          /** WPA/IEEE 802.11i/D3.0
           * @deprecated Due to security and performance limitations, use of WPA-1 networks
           * is discouraged. WPA-2 (RSN) should be used instead. */
          @Deprecated
          public static final int WPA = 0;
          /** RSN WPA2/WPA3/IEEE 802.11i */
          public static final int RSN = 1;
          /** HS2.0 r2 OSEN
           * @hide
           */
          public static final int OSEN = 2;
  
          public static final String varName = "proto";
  
          public static final String[] strings = { "WPA", "RSN", "OSEN" };
      }
  
      /**
       * Recognized IEEE 802.11 authentication algorithms.
       */
      public static class AuthAlgorithm {
          private AuthAlgorithm() { }
  
          /** Open System authentication (required for WPA/WPA2) */
          public static final int OPEN = 0;
          /** Shared Key authentication (requires static WEP keys)
           * @deprecated Due to security and performance limitations, use of WEP networks
           * is discouraged. */
          @Deprecated
          public static final int SHARED = 1;
          /** LEAP/Network EAP (only used with LEAP) */
          public static final int LEAP = 2;
  
          public static final String varName = "auth_alg";
  
          public static final String[] strings = { "OPEN", "SHARED", "LEAP" };
      }
  
      /**
       * Recognized pairwise ciphers for WPA.
       */
      public static class PairwiseCipher {
          private PairwiseCipher() { }
  
          /** Use only Group keys (deprecated) */
          public static final int NONE = 0;
          /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
           * @deprecated Due to security and performance limitations, use of WPA-1 networks
           * is discouraged. WPA-2 (RSN) should be used instead. */
          @Deprecated
          public static final int TKIP = 1;
          /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
          public static final int CCMP = 2;
          /**
           * AES in Galois/Counter Mode
           */
          public static final int GCMP_256 = 3;
  
          public static final String varName = "pairwise";
  
          public static final String[] strings = { "NONE", "TKIP", "CCMP", "GCMP_256" };
      }
  
      /**
       * Recognized group ciphers.
       * <pre>
       * CCMP = AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0]
       * TKIP = Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
       * WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
       * WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
       * GCMP_256 = AES in Galois/Counter Mode
       * </pre>
       */
      public static class GroupCipher {
          private GroupCipher() { }
  
          /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
           * @deprecated Due to security and performance limitations, use of WEP networks
           * is discouraged. */
          @Deprecated
          public static final int WEP40 = 0;
          /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
           * @deprecated Due to security and performance limitations, use of WEP networks
           * is discouraged. */
          @Deprecated
          public static final int WEP104 = 1;
          /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
          public static final int TKIP = 2;
          /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
          public static final int CCMP = 3;
          /** Hotspot 2.0 r2 OSEN
           * @hide
           */
          public static final int GTK_NOT_USED = 4;
          /**
           * AES in Galois/Counter Mode
           */
          public static final int GCMP_256 = 5;
  
          public static final String varName = "group";
  
          public static final String[] strings =
                  { /* deprecated */ "WEP40", /* deprecated */ "WEP104",
                          "TKIP", "CCMP", "GTK_NOT_USED", "GCMP_256" };
      }
  
      /**
       * Recognized group management ciphers.
       * <pre>
       * BIP_CMAC_256 = Cipher-based Message Authentication Code 256 bits
       * BIP_GMAC_128 = Galois Message Authentication Code 128 bits
       * BIP_GMAC_256 = Galois Message Authentication Code 256 bits
       * </pre>
       */
      public static class GroupMgmtCipher {
          private GroupMgmtCipher() { }
  
          /** CMAC-256 = Cipher-based Message Authentication Code */
          public static final int BIP_CMAC_256 = 0;
  
          /** GMAC-128 = Galois Message Authentication Code */
          public static final int BIP_GMAC_128 = 1;
  
          /** GMAC-256 = Galois Message Authentication Code */
          public static final int BIP_GMAC_256 = 2;
  
          private static final String varName = "groupMgmt";
  
          private static final String[] strings = { "BIP_CMAC_256",
                  "BIP_GMAC_128", "BIP_GMAC_256"};
      }
  
      /**
       * Recognized suiteB ciphers.
       * <pre>
       * ECDHE_ECDSA
       * ECDHE_RSA
       * </pre>
       * @hide
       */
      public static class SuiteBCipher {
          private SuiteBCipher() { }
  
          /** Diffie-Hellman with Elliptic Curve_ECDSA signature */
          public static final int ECDHE_ECDSA = 0;
  
          /** Diffie-Hellman with_RSA signature */
          public static final int ECDHE_RSA = 1;
  
          private static final String varName = "SuiteB";
  
          private static final String[] strings = { "ECDHE_ECDSA", "ECDHE_RSA" };
      }
  
      /** Possible status of a network configuration. */
      public static class Status {
          private Status() { }
  
          /** this is the network we are currently connected to */
          public static final int CURRENT = 0;
          /** supplicant will not attempt to use this network */
          public static final int DISABLED = 1;
          /** supplicant will consider this network available for association */
          public static final int ENABLED = 2;
  
          public static final String[] strings = { "current", "disabled", "enabled" };
      }
  
      /**
       * Security types we support.
       */
      /** @hide */
      public static final int SECURITY_TYPE_OPEN = 0;
      /** @hide */
      public static final int SECURITY_TYPE_WEP = 1;
      /** @hide */
      public static final int SECURITY_TYPE_PSK = 2;
      /** @hide */
      public static final int SECURITY_TYPE_EAP = 3;
      /** @hide */
      public static final int SECURITY_TYPE_SAE = 4;
      /** @hide */
      public static final int SECURITY_TYPE_EAP_SUITE_B = 5;
      /** @hide */
      public static final int SECURITY_TYPE_OWE = 6;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
              SECURITY_TYPE_OPEN,
              SECURITY_TYPE_WEP,
              SECURITY_TYPE_PSK,
              SECURITY_TYPE_EAP,
              SECURITY_TYPE_SAE,
              SECURITY_TYPE_EAP_SUITE_B,
              SECURITY_TYPE_OWE
      })
      public @interface SecurityType {}
  
      /**
       * @hide
       * Set security params (sets the various bitsets exposed in WifiConfiguration).
       *
       * @param securityType One of the security types from {@link SecurityType}.
       */
      public void setSecurityParams(@SecurityType int securityType) {
          // Clear all the bitsets.
          allowedKeyManagement.clear();
          allowedProtocols.clear();
          allowedAuthAlgorithms.clear();
          allowedPairwiseCiphers.clear();
          allowedGroupCiphers.clear();
          allowedGroupManagementCiphers.clear();
          allowedSuiteBCiphers.clear();
  
          switch (securityType) {
              case SECURITY_TYPE_OPEN:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                  break;
              case SECURITY_TYPE_WEP:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
                  allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
                  allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
                  break;
              case SECURITY_TYPE_PSK:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
                  break;
              case SECURITY_TYPE_EAP:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
                  break;
              case SECURITY_TYPE_SAE:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
                  requirePMF = true;
                  break;
              case SECURITY_TYPE_EAP_SUITE_B:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
                  allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                  allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
                  // Note: allowedSuiteBCiphers bitset will be set by the service once the
                  // certificates are attached to this profile
                  requirePMF = true;
                  break;
              case SECURITY_TYPE_OWE:
                  allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
                  requirePMF = true;
                  break;
              default:
                  throw new IllegalArgumentException("unknown security type " + securityType);
          }
      }
  
      /** @hide */
      public static final int UNKNOWN_UID = -1;
  
      /**
       * The ID number that the supplicant uses to identify this
       * network configuration entry. This must be passed as an argument
       * to most calls into the supplicant.
       */
      public int networkId;
  
      // Fixme We need remove this field to use only Quality network selection status only
      /**
       * The current status of this network configuration entry.
       * @see Status
       */
      public int status;
  
      /**
       * The network's SSID. Can either be a UTF-8 string,
       * which must be enclosed in double quotation marks
       * (e.g., {@code "MyNetwork"}), or a string of
       * hex digits, which are not enclosed in quotes
       * (e.g., {@code 01a243f405}).
       */
      public String SSID;
  
      /**
       * When set, this network configuration entry should only be used when
       * associating with the AP having the specified BSSID. The value is
       * a string in the format of an Ethernet MAC address, e.g.,
       * <code>XX:XX:XX:XX:XX:XX</code> where each <code>X</code> is a hex digit.
       */
      public String BSSID;
  
      /**
       * 2GHz band.
       * @hide
       */
      public static final int AP_BAND_2GHZ = 0;
  
      /**
       * 5GHz band.
       * @hide
       */
      public static final int AP_BAND_5GHZ = 1;
  
      /**
       * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
       * operating country code and current radio conditions.
       * @hide
       */
      public static final int AP_BAND_ANY = -1;
  
      /**
       * The band which AP resides on
       * -1:Any 0:2G 1:5G
       * By default, 2G is chosen
       * @hide
       */
      @UnsupportedAppUsage
      public int apBand = AP_BAND_2GHZ;
  
      /**
       * The channel which AP resides on,currently, US only
       * 2G  1-11
       * 5G  36,40,44,48,149,153,157,161,165
       * 0 - find a random available channel according to the apBand
       * @hide
       */
      @UnsupportedAppUsage
      public int apChannel = 0;
  
      /**
       * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in
       * double quotation marks (e.g., {@code "abcdefghij"} for PSK passphrase or
       * a string of 64 hex digits for raw PSK.
       * <p/>
       * When the value of this key is read, the actual key is
       * not returned, just a "*" if the key has a value, or the null
       * string otherwise.
       */
      public String preSharedKey;
  
      /**
       * Four WEP keys. For each of the four values, provide either an ASCII
       * string enclosed in double quotation marks (e.g., {@code "abcdef"}),
       * a string of hex digits (e.g., {@code 0102030405}), or an empty string
       * (e.g., {@code ""}).
       * <p/>
       * When the value of one of these keys is read, the actual key is
       * not returned, just a "*" if the key has a value, or the null
       * string otherwise.
       * @deprecated Due to security and performance limitations, use of WEP networks
       * is discouraged.
       */
      @Deprecated
      public String[] wepKeys;
  
      /** Default WEP key index, ranging from 0 to 3.
       * @deprecated Due to security and performance limitations, use of WEP networks
       * is discouraged. */
      @Deprecated
      public int wepTxKeyIndex;
  
      /**
       * Priority determines the preference given to a network by {@code wpa_supplicant}
       * when choosing an access point with which to associate.
       * @deprecated This field does not exist anymore.
       */
      @Deprecated
      public int priority;
  
      /**
       * This is a network that does not broadcast its SSID, so an
       * SSID-specific probe request must be used for scans.
       */
      public boolean hiddenSSID;
  
      /**
       * This is a network that requries Protected Management Frames (PMF).
       * @hide
       */
      public boolean requirePMF;
  
      /**
       * Update identifier, for Passpoint network.
       * @hide
       */
      public String updateIdentifier;
  
      /**
       * The set of key management protocols supported by this configuration.
       * See {@link KeyMgmt} for descriptions of the values.
       * Defaults to WPA-PSK WPA-EAP.
       */
      @NonNull
      public BitSet allowedKeyManagement;
      /**
       * The set of security protocols supported by this configuration.
       * See {@link Protocol} for descriptions of the values.
       * Defaults to WPA RSN.
       */
      @NonNull
      public BitSet allowedProtocols;
      /**
       * The set of authentication protocols supported by this configuration.
       * See {@link AuthAlgorithm} for descriptions of the values.
       * Defaults to automatic selection.
       */
      @NonNull
      public BitSet allowedAuthAlgorithms;
      /**
       * The set of pairwise ciphers for WPA supported by this configuration.
       * See {@link PairwiseCipher} for descriptions of the values.
       * Defaults to CCMP TKIP.
       */
      @NonNull
      public BitSet allowedPairwiseCiphers;
      /**
       * The set of group ciphers supported by this configuration.
       * See {@link GroupCipher} for descriptions of the values.
       * Defaults to CCMP TKIP WEP104 WEP40.
       */
      @NonNull
      public BitSet allowedGroupCiphers;
      /**
       * The set of group management ciphers supported by this configuration.
       * See {@link GroupMgmtCipher} for descriptions of the values.
       */
      @NonNull
      public BitSet allowedGroupManagementCiphers;
      /**
       * The set of SuiteB ciphers supported by this configuration.
       * To be used for WPA3-Enterprise mode.
       * See {@link SuiteBCipher} for descriptions of the values.
       */
      @NonNull
      public BitSet allowedSuiteBCiphers;
      /**
       * The enterprise configuration details specifying the EAP method,
       * certificates and other settings associated with the EAP.
       */
      public WifiEnterpriseConfig enterpriseConfig;
  
      /**
       * Fully qualified domain name of a Passpoint configuration
       */
      public String FQDN;
  
      /**
       * Name of Passpoint credential provider
       */
      public String providerFriendlyName;
  
      /**
       * Flag indicating if this network is provided by a home Passpoint provider or a roaming
       * Passpoint provider.  This flag will be {@code true} if this network is provided by
       * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
       * or is a non-Passpoint network.
       */
      public boolean isHomeProviderNetwork;
  
      /**
       * Roaming Consortium Id list for Passpoint credential; identifies a set of networks where
       * Passpoint credential will be considered valid
       */
      public long[] roamingConsortiumIds;
  
      /**
       * @hide
       * This network configuration is visible to and usable by other users on the
       * same device.
       */
      @UnsupportedAppUsage
      public boolean shared;
  
      /**
       * @hide
       */
      @NonNull
      @UnsupportedAppUsage
      private IpConfiguration mIpConfiguration;
  
      /**
       * @hide
       * dhcp server MAC address if known
       */
      public String dhcpServer;
  
      /**
       * @hide
       * default Gateway MAC address if known
       */
      @UnsupportedAppUsage
      public String defaultGwMacAddress;
  
      /**
       * @hide
       * last time we connected, this configuration had validated internet access
       */
      @UnsupportedAppUsage
      public boolean validatedInternetAccess;
  
      /**
       * @hide
       * The number of beacon intervals between Delivery Traffic Indication Maps (DTIM)
       * This value is populated from scan results that contain Beacon Frames, which are infrequent.
       * The value is not guaranteed to be set or current (Although it SHOULDNT change once set)
       * Valid values are from 1 - 255. Initialized here as 0, use this to check if set.
       */
      public int dtimInterval = 0;
  
      /**
       * Flag indicating if this configuration represents a legacy Passpoint configuration
       * (Release N or older).  This is used for migrating Passpoint configuration from N to O.
       * This will no longer be needed after O.
       * @hide
       */
      public boolean isLegacyPasspointConfig = false;
      /**
       * @hide
       * Uid of app creating the configuration
       */
      @SystemApi
      public int creatorUid;
  
      /**
       * @hide
       * Uid of last app issuing a connection related command
       */
      @UnsupportedAppUsage
      public int lastConnectUid;
  
      /**
       * @hide
       * Uid of last app modifying the configuration
       */
      @SystemApi
      public int lastUpdateUid;
  
      /**
       * @hide
       * Universal name for app creating the configuration
       *    see {@link PackageManager#getNameForUid(int)}
       */
      @SystemApi
      public String creatorName;
  
      /**
       * @hide
       * Universal name for app updating the configuration
       *    see {@link PackageManager#getNameForUid(int)}
       */
      @SystemApi
      public String lastUpdateName;
  
      /**
       * @hide
       * Status of user approval for connection
       */
      public int userApproved = USER_UNSPECIFIED;
  
      /** The Below RSSI thresholds are used to configure AutoJoin
       *  - GOOD/LOW/BAD thresholds are used so as to calculate link score
       *  - UNWANTED_SOFT are used by the blacklisting logic so as to handle
       *  the unwanted network message coming from CS
       *  - UNBLACKLIST thresholds are used so as to tweak the speed at which
       *  the network is unblacklisted (i.e. if
       *          it is seen with good RSSI, it is blacklisted faster)
       *  - INITIAL_AUTOJOIN_ATTEMPT, used to determine how close from
       *  the network we need to be before autojoin kicks in
       */
      /** @hide **/
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public static int INVALID_RSSI = -127;
  
      // States for the userApproved field
      /**
       * @hide
       * User hasn't specified if connection is okay
       */
      public static final int USER_UNSPECIFIED = 0;
      /**
       * @hide
       * User has approved this for connection
       */
      public static final int USER_APPROVED = 1;
      /**
       * @hide
       * User has banned this from connection
       */
      public static final int USER_BANNED = 2;
      /**
       * @hide
       * Waiting for user input
       */
      public static final int USER_PENDING = 3;
  
      /**
       * @hide
       * Number of reports indicating no Internet Access
       */
      @UnsupportedAppUsage
      public int numNoInternetAccessReports;
  
      /**
       * @hide
       * For debug: date at which the config was last updated
       */
      public String updateTime;
  
      /**
       * @hide
       * For debug: date at which the config was last updated
       */
      public String creationTime;
  
      /**
       * @hide
       * The WiFi configuration is considered to have no internet access for purpose of autojoining
       * if there has been a report of it having no internet access, and, it never have had
       * internet access in the past.
       */
      @SystemApi
      public boolean hasNoInternetAccess() {
          return numNoInternetAccessReports > 0 && !validatedInternetAccess;
      }
  
      /**
       * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a
       * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to
       * this configuration and selects "don't ask again".
       * @hide
       */
      @UnsupportedAppUsage
      public boolean noInternetAccessExpected;
  
      /**
       * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a
       * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to
       * this configuration and selects "don't ask again".
       * @hide
       */
      @SystemApi
      public boolean isNoInternetAccessExpected() {
          return noInternetAccessExpected;
      }
  
      /**
       * This Wifi configuration is expected for OSU(Online Sign Up) of Passpoint Release 2.
       * @hide
       */
      public boolean osu;
  
      /**
       * @hide
       * Last time the system was connected to this configuration.
       */
      public long lastConnected;
  
      /**
       * @hide
       * Last time the system was disconnected to this configuration.
       */
      public long lastDisconnected;
  
      /**
       * Set if the configuration was self added by the framework
       * This boolean is cleared if we get a connect/save/ update or
       * any wifiManager command that indicate the user interacted with the configuration
       * since we will now consider that the configuration belong to him.
       * @hide
       */
      @UnsupportedAppUsage
      public boolean selfAdded;
  
      /**
       * Set if the configuration was self added by the framework
       * This boolean is set once and never cleared. It is used
       * so as we never loose track of who created the
       * configuration in the first place.
       * @hide
       */
      public boolean didSelfAdd;
  
      /**
       * Peer WifiConfiguration this WifiConfiguration was added for
       * @hide
       */
      public String peerWifiConfiguration;
  
      /**
       * @hide
       * Indicate that a WifiConfiguration is temporary and should not be saved
       * nor considered by AutoJoin.
       */
      public boolean ephemeral;
  
      /**
       * @hide
       * Indicate that a WifiConfiguration is temporary and should not be saved
       * nor considered by AutoJoin.
       */
      @SystemApi
      public boolean isEphemeral() {
        return ephemeral;
      }
  
      /**
       * Indicate whther the network is trusted or not. Networks are considered trusted
       * if the user explicitly allowed this network connection.
       * @hide
       */
      public boolean trusted;
  
      /**
       * This Wifi configuration is created from a {@link WifiNetworkSuggestion}
       * @hide
       */
      public boolean fromWifiNetworkSuggestion;
  
      /**
       * This Wifi configuration is created from a {@link WifiNetworkSpecifier}
       * @hide
       */
      public boolean fromWifiNetworkSpecifier;
  
      /**
       * Indicates if the creator of this configuration has expressed that it
       * should be considered metered.
       *
       * @see #isMetered(WifiConfiguration, WifiInfo)
       * @hide
       */
      @SystemApi
      public boolean meteredHint;
  
      /** {@hide} */
      public static final int METERED_OVERRIDE_NONE = 0;
      /** {@hide} */
      public static final int METERED_OVERRIDE_METERED = 1;
      /** {@hide} */
      public static final int METERED_OVERRIDE_NOT_METERED = 2;
  
      /**
       * Indicates if the end user has expressed an explicit opinion about the
       * meteredness of this network, such as through the Settings app.
       * <p>
       * This should always override any values from {@link #meteredHint} or
       * {@link WifiInfo#getMeteredHint()}.
       *
       * @see #isMetered(WifiConfiguration, WifiInfo)
       * @hide
       */
      public int meteredOverride = METERED_OVERRIDE_NONE;
  
      /**
       * Blend together all the various opinions to decide if the given network
       * should be considered metered or not.
       *
       * @hide
       */
      public static boolean isMetered(WifiConfiguration config, WifiInfo info) {
          boolean metered = false;
          if (info != null && info.getMeteredHint()) {
              metered = true;
          }
          if (config != null && config.meteredHint) {
              metered = true;
          }
          if (config != null
                  && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) {
              metered = true;
          }
          if (config != null
                  && config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) {
              metered = false;
          }
          return metered;
      }
  
      /**
       * @hide
       * Returns true if this WiFi config is for an open network.
       */
      public boolean isOpenNetwork() {
          final int cardinality = allowedKeyManagement.cardinality();
          final boolean hasNoKeyMgmt = cardinality == 0
                  || (cardinality == 1 && (allowedKeyManagement.get(KeyMgmt.NONE)
                  || allowedKeyManagement.get(KeyMgmt.OWE)));
  
          boolean hasNoWepKeys = true;
          if (wepKeys != null) {
              for (int i = 0; i < wepKeys.length; i++) {
                  if (wepKeys[i] != null) {
                      hasNoWepKeys = false;
                      break;
                  }
              }
          }
  
          return hasNoKeyMgmt && hasNoWepKeys;
      }
  
      /**
       * @hide
       * Setting this value will force scan results associated with this configuration to
       * be included in the bucket of networks that are externally scored.
       * If not set, associated scan results will be treated as legacy saved networks and
       * will take precedence over networks in the scored category.
       */
      @SystemApi
      public boolean useExternalScores;
  
      /**
       * @hide
       * Number of time the scorer overrode a the priority based choice, when comparing two
       * WifiConfigurations, note that since comparing WifiConfiguration happens very often
       * potentially at every scan, this number might become very large, even on an idle
       * system.
       */
      @SystemApi
      public int numScorerOverride;
  
      /**
       * @hide
       * Number of time the scorer overrode a the priority based choice, and the comparison
       * triggered a network switch
       */
      @SystemApi
      public int numScorerOverrideAndSwitchedNetwork;
  
      /**
       * @hide
       * Number of time we associated to this configuration.
       */
      @SystemApi
      public int numAssociation;
  
      /**
       * @hide
       * Use factory MAC when connecting to this network
       */
      public static final int RANDOMIZATION_NONE = 0;
      /**
       * @hide
       * Generate a randomized MAC once and reuse it for all connections to this network
       */
      public static final int RANDOMIZATION_PERSISTENT = 1;
  
      /**
       * @hide
       * Level of MAC randomization for this network
       */
      public int macRandomizationSetting = RANDOMIZATION_PERSISTENT;
  
      /**
       * @hide
       * Randomized MAC address to use with this particular network
       */
      @NonNull
      private MacAddress mRandomizedMacAddress;
  
      /**
       * @hide
       * Checks if the given MAC address can be used for Connected Mac Randomization
       * by verifying that it is non-null, unicast, locally assigned, and not default mac.
       * @param mac MacAddress to check
       * @return true if mac is good to use
       */
      public static boolean isValidMacAddressForRandomization(MacAddress mac) {
          return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
                  && !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
      }
  
      /**
       * @hide
       * Returns Randomized MAC address to use with the network.
       * If it is not set/valid, creates a new randomized address.
       * If it can't generate a valid mac, returns the default MAC.
       */
      public @NonNull MacAddress getOrCreateRandomizedMacAddress() {
          int randomMacGenerationCount = 0;
          while (!isValidMacAddressForRandomization(mRandomizedMacAddress)
                  && randomMacGenerationCount < MAXIMUM_RANDOM_MAC_GENERATION_RETRY) {
              mRandomizedMacAddress = MacAddress.createRandomUnicastAddress();
              randomMacGenerationCount++;
          }
  
          if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) {
              mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
          }
          return mRandomizedMacAddress;
      }
  
      /**
       * Returns MAC address set to be the local randomized MAC address.
       * Depending on user preference, the device may or may not use the returned MAC address for
       * connections to this network.
       * <p>
       * Information is restricted to Device Owner, Profile Owner, and Carrier apps
       * (which will only obtain addresses for configurations which they create). Other callers
       * will receive a default "02:00:00:00:00:00" MAC address.
       */
      public @NonNull MacAddress getRandomizedMacAddress() {
          return mRandomizedMacAddress;
      }
  
      /**
       * @hide
       * @param mac MacAddress to change into
       */
      public void setRandomizedMacAddress(@NonNull MacAddress mac) {
          if (mac == null) {
              Log.e(TAG, "setRandomizedMacAddress received null MacAddress.");
              return;
          }
          mRandomizedMacAddress = mac;
      }
  
      /** @hide
       * Boost given to RSSI on a home network for the purpose of calculating the score
       * This adds stickiness to home networks, as defined by:
       * - less than 4 known BSSIDs
       * - PSK only
       * - TODO: add a test to verify that all BSSIDs are behind same gateway
       ***/
      public static final int HOME_NETWORK_RSSI_BOOST = 5;
  
      /**
       * @hide
       * This class is used to contain all the information and API used for quality network selection
       */
      public static class NetworkSelectionStatus {
          /**
           * Quality Network Selection Status enable, temporary disabled, permanently disabled
           */
          /**
           * This network is allowed to join Quality Network Selection
           */
          public static final int NETWORK_SELECTION_ENABLED = 0;
          /**
           * network was temporary disabled. Can be re-enabled after a time period expire
           */
          public static final int NETWORK_SELECTION_TEMPORARY_DISABLED  = 1;
          /**
           * network was permanently disabled.
           */
          public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED  = 2;
          /**
           * Maximum Network selection status
           */
          public static final int NETWORK_SELECTION_STATUS_MAX = 3;
  
          /**
           * Quality network selection status String (for debug purpose). Use Quality network
           * selection status value as index to extec the corresponding debug string
           */
          public static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
                  "NETWORK_SELECTION_ENABLED",
                  "NETWORK_SELECTION_TEMPORARY_DISABLED",
                  "NETWORK_SELECTION_PERMANENTLY_DISABLED"};
  
          //Quality Network disabled reasons
          /**
           * Default value. Means not disabled
           */
          public static final int NETWORK_SELECTION_ENABLE = 0;
          /**
           * The starting index for network selection disabled reasons
           */
          public static final int NETWORK_SELECTION_DISABLED_STARTING_INDEX = 1;
          /**
           * @deprecated it is not used any more.
           * This network is disabled because higher layer (>2) network is bad
           */
          public static final int DISABLED_BAD_LINK = 1;
          /**
           * This network is disabled because multiple association rejects
           */
          public static final int DISABLED_ASSOCIATION_REJECTION = 2;
          /**
           * This network is disabled because multiple authentication failure
           */
          public static final int DISABLED_AUTHENTICATION_FAILURE = 3;
          /**
           * This network is disabled because multiple DHCP failure
           */
          public static final int DISABLED_DHCP_FAILURE = 4;
          /**
           * This network is disabled because of security network but no credentials
           */
          public static final int DISABLED_DNS_FAILURE = 5;
          /**
           * This network is temporarily disabled because it has no Internet access.
           */
          public static final int DISABLED_NO_INTERNET_TEMPORARY = 6;
          /**
           * This network is disabled because we started WPS
           */
          public static final int DISABLED_WPS_START = 7;
          /**
           * This network is disabled because EAP-TLS failure
           */
          public static final int DISABLED_TLS_VERSION_MISMATCH = 8;
          // Values above are for temporary disablement; values below are for permanent disablement.
          /**
           * This network is disabled due to absence of user credentials
           */
          public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 9;
          /**
           * This network is permanently disabled because it has no Internet access and user does not
           * want to stay connected.
           */
          public static final int DISABLED_NO_INTERNET_PERMANENT = 10;
          /**
           * This network is disabled due to WifiManager disable it explicitly
           */
          public static final int DISABLED_BY_WIFI_MANAGER = 11;
          /**
           * This network is disabled due to user switching
           */
          public static final int DISABLED_DUE_TO_USER_SWITCH = 12;
          /**
           * This network is disabled due to wrong password
           */
          public static final int DISABLED_BY_WRONG_PASSWORD = 13;
          /**
           * This network is disabled because service is not subscribed
           */
          public static final int DISABLED_AUTHENTICATION_NO_SUBSCRIPTION = 14;
          /**
           * This Maximum disable reason value
           */
          public static final int NETWORK_SELECTION_DISABLED_MAX = 15;
  
          /**
           * Quality network selection disable reason String (for debug purpose)
           */
          public static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
                  "NETWORK_SELECTION_ENABLE",
                  "NETWORK_SELECTION_DISABLED_BAD_LINK", // deprecated
                  "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
                  "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
                  "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
                  "NETWORK_SELECTION_DISABLED_DNS_FAILURE",
                  "NETWORK_SELECTION_DISABLED_NO_INTERNET_TEMPORARY",
                  "NETWORK_SELECTION_DISABLED_WPS_START",
                  "NETWORK_SELECTION_DISABLED_TLS_VERSION",
                  "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
                  "NETWORK_SELECTION_DISABLED_NO_INTERNET_PERMANENT",
                  "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
                  "NETWORK_SELECTION_DISABLED_BY_USER_SWITCH",
                  "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD",
                  "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_SUBSCRIPTION"
          };
  
          /**
           * Invalid time stamp for network selection disable
           */
          public static final long INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP = -1L;
  
          /**
           *  This constant indicates the current configuration has connect choice set
           */
          private static final int CONNECT_CHOICE_EXISTS = 1;
  
          /**
           *  This constant indicates the current configuration does not have connect choice set
           */
          private static final int CONNECT_CHOICE_NOT_EXISTS = -1;
  
          // fields for QualityNetwork Selection
          /**
           * Network selection status, should be in one of three status: enable, temporaily disabled
           * or permanently disabled
           */
          private int mStatus;
  
          /**
           * Reason for disable this network
           */
          private int mNetworkSelectionDisableReason;
  
          /**
           * Last time we temporarily disabled the configuration
           */
          private long mTemporarilyDisabledTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
  
          /**
           * counter for each Network selection disable reason
           */
          private int[] mNetworkSeclectionDisableCounter = new int[NETWORK_SELECTION_DISABLED_MAX];
  
          /**
           * Connect Choice over this configuration
           *
           * When current wifi configuration is visible to the user but user explicitly choose to
           * connect to another network X, the another networks X's configure key will be stored here.
           * We will consider user has a preference of X over this network. And in the future,
           * network selection will always give X a higher preference over this configuration.
           * configKey is : "SSID"-WEP-WPA_PSK-WPA_EAP
           */
          private String mConnectChoice;
  
          /**
           * The system timestamp when we records the connectChoice. This value is obtained from
           * System.currentTimeMillis
           */
          private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
  
          /**
           * Used to cache the temporary candidate during the network selection procedure. It will be
           * kept updating once a new scan result has a higher score than current one
           */
          private ScanResult mCandidate;
  
          /**
           * Used to cache the score of the current temporary candidate during the network
           * selection procedure.
           */
          private int mCandidateScore;
  
          /**
           * Indicate whether this network is visible in latest Qualified Network Selection. This
           * means there is scan result found related to this Configuration and meet the minimum
           * requirement. The saved network need not join latest Qualified Network Selection. For
           * example, it is disabled. True means network is visible in latest Qualified Network
           * Selection and false means network is invisible
           */
          private boolean mSeenInLastQualifiedNetworkSelection;
  
          /**
           * Boolean indicating if we have ever successfully connected to this network.
           *
           * This value will be set to true upon a successful connection.
           * This value will be set to false if a previous value was not stored in the config or if
           * the credentials are updated (ex. a password change).
           */
          private boolean mHasEverConnected;
  
          /**
           * Boolean indicating whether {@link com.android.server.wifi.RecommendedNetworkEvaluator}
           * chose not to connect to this network in the last qualified network selection process.
           */
          private boolean mNotRecommended;
  
          /**
           * Set whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
           * recommend connecting to this network.
           */
          public void setNotRecommended(boolean notRecommended) {
              mNotRecommended = notRecommended;
          }
  
          /**
           * Returns whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
           * recommend connecting to this network.
           */
          public boolean isNotRecommended() {
              return mNotRecommended;
          }
  
          /**
           * set whether this network is visible in latest Qualified Network Selection
           * @param seen value set to candidate
           */
          public void setSeenInLastQualifiedNetworkSelection(boolean seen) {
              mSeenInLastQualifiedNetworkSelection =  seen;
          }
  
          /**
           * get whether this network is visible in latest Qualified Network Selection
           * @return returns true -- network is visible in latest Qualified Network Selection
           *         false -- network is invisible in latest Qualified Network Selection
           */
          public boolean getSeenInLastQualifiedNetworkSelection() {
              return mSeenInLastQualifiedNetworkSelection;
          }
          /**
           * set the temporary candidate of current network selection procedure
           * @param scanCandidate {@link ScanResult} the candidate set to mCandidate
           */
          public void setCandidate(ScanResult scanCandidate) {
              mCandidate = scanCandidate;
          }
  
          /**
           * get the temporary candidate of current network selection procedure
           * @return  returns {@link ScanResult} temporary candidate of current network selection
           * procedure
           */
          public ScanResult getCandidate() {
              return mCandidate;
          }
  
          /**
           * set the score of the temporary candidate of current network selection procedure
           * @param score value set to mCandidateScore
           */
          public void setCandidateScore(int score) {
              mCandidateScore = score;
          }
  
          /**
           * get the score of the temporary candidate of current network selection procedure
           * @return returns score of the temporary candidate of current network selection procedure
           */
          public int getCandidateScore() {
              return mCandidateScore;
          }
  
          /**
           * get user preferred choice over this configuration
           *@return returns configKey of user preferred choice over this configuration
           */
          public String getConnectChoice() {
              return mConnectChoice;
          }
  
          /**
           * set user preferred choice over this configuration
           * @param newConnectChoice, the configKey of user preferred choice over this configuration
           */
          public void setConnectChoice(String newConnectChoice) {
              mConnectChoice = newConnectChoice;
          }
  
          /**
           * get the timeStamp when user select a choice over this configuration
           * @return returns when current connectChoice is set (time from System.currentTimeMillis)
           */
          public long getConnectChoiceTimestamp() {
              return mConnectChoiceTimestamp;
          }
  
          /**
           * set the timeStamp when user select a choice over this configuration
           * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should
           *        be obtained from System.currentTimeMillis
           */
          public void setConnectChoiceTimestamp(long timeStamp) {
              mConnectChoiceTimestamp = timeStamp;
          }
  
          /**
           * get current Quality network selection status
           * @return returns current Quality network selection status in String (for debug purpose)
           */
          public String getNetworkStatusString() {
              return QUALITY_NETWORK_SELECTION_STATUS[mStatus];
          }
  
          public void setHasEverConnected(boolean value) {
              mHasEverConnected = value;
          }
  
          public boolean getHasEverConnected() {
              return mHasEverConnected;
          }
  
          public NetworkSelectionStatus() {
              // previously stored configs will not have this parameter, so we default to false.
              mHasEverConnected = false;
          };
  
          /**
           * @param reason specific error reason
           * @return  corresponding network disable reason String (for debug purpose)
           */
          public static String getNetworkDisableReasonString(int reason) {
              if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  return QUALITY_NETWORK_SELECTION_DISABLE_REASON[reason];
              } else {
                  return null;
              }
          }
          /**
           * get current network disable reason
           * @return current network disable reason in String (for debug purpose)
           */
          public String getNetworkDisableReasonString() {
              return QUALITY_NETWORK_SELECTION_DISABLE_REASON[mNetworkSelectionDisableReason];
          }
  
          /**
           * get current network network selection status
           * @return return current network network selection status
           */
          public int getNetworkSelectionStatus() {
              return mStatus;
          }
          /**
           * @return whether current network is enabled to join network selection
           */
          public boolean isNetworkEnabled() {
              return mStatus == NETWORK_SELECTION_ENABLED;
          }
  
          /**
           * @return whether current network is temporary disabled
           */
          public boolean isNetworkTemporaryDisabled() {
              return mStatus == NETWORK_SELECTION_TEMPORARY_DISABLED;
          }
  
          /**
           * @return returns whether current network is permanently disabled
           */
          public boolean isNetworkPermanentlyDisabled() {
              return mStatus == NETWORK_SELECTION_PERMANENTLY_DISABLED;
          }
  
          /**
           * set current networ work selection status
           * @param status network selection status to set
           */
          public void setNetworkSelectionStatus(int status) {
              if (status >= 0 && status < NETWORK_SELECTION_STATUS_MAX) {
                  mStatus = status;
              }
          }
  
          /**
           * @return returns current network's disable reason
           */
          public int getNetworkSelectionDisableReason() {
              return mNetworkSelectionDisableReason;
          }
  
          /**
           * set Network disable reason
           * @param  reason Network disable reason
           */
          public void setNetworkSelectionDisableReason(int reason) {
              if (reason >= 0 && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  mNetworkSelectionDisableReason = reason;
              } else {
                  throw new IllegalArgumentException("Illegal reason value: " + reason);
              }
          }
  
          /**
           * check whether network is disabled by this reason
           * @param reason a specific disable reason
           * @return true -- network is disabled for this reason
           *         false -- network is not disabled for this reason
           */
          public boolean isDisabledByReason(int reason) {
              return mNetworkSelectionDisableReason == reason;
          }
  
          /**
           * @param timeStamp Set when current network is disabled in millisecond since January 1,
           * 1970 00:00:00.0 UTC
           */
          public void setDisableTime(long timeStamp) {
              mTemporarilyDisabledTimestamp = timeStamp;
          }
  
          /**
           * @return returns when current network is disabled in millisecond since January 1,
           * 1970 00:00:00.0 UTC
           */
          public long getDisableTime() {
              return mTemporarilyDisabledTimestamp;
          }
  
          /**
           * get the disable counter of a specific reason
           * @param  reason specific failure reason
           * @exception throw IllegalArgumentException for illegal input
           * @return counter number for specific error reason.
           */
          public int getDisableReasonCounter(int reason) {
              if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  return mNetworkSeclectionDisableCounter[reason];
              } else {
                  throw new IllegalArgumentException("Illegal reason value: " + reason);
              }
          }
  
          /**
           * set the counter of a specific failure reason
           * @param reason reason for disable error
           * @param value the counter value for this specific reason
           * @exception throw IllegalArgumentException for illegal input
           */
          public void setDisableReasonCounter(int reason, int value) {
              if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  mNetworkSeclectionDisableCounter[reason] = value;
              } else {
                  throw new IllegalArgumentException("Illegal reason value: " + reason);
              }
          }
  
          /**
           * increment the counter of a specific failure reason
           * @param reason a specific failure reason
           * @exception throw IllegalArgumentException for illegal input
           */
          public void incrementDisableReasonCounter(int reason) {
              if (reason >= NETWORK_SELECTION_ENABLE  && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  mNetworkSeclectionDisableCounter[reason]++;
              } else {
                  throw new IllegalArgumentException("Illegal reason value: " + reason);
              }
          }
  
          /**
           * clear the counter of a specific failure reason
           * @hide
           * @param reason a specific failure reason
           * @exception throw IllegalArgumentException for illegal input
           */
          public void clearDisableReasonCounter(int reason) {
              if (reason >= NETWORK_SELECTION_ENABLE && reason < NETWORK_SELECTION_DISABLED_MAX) {
                  mNetworkSeclectionDisableCounter[reason] = NETWORK_SELECTION_ENABLE;
              } else {
                  throw new IllegalArgumentException("Illegal reason value: " + reason);
              }
          }
  
          /**
           * clear all the failure reason counters
           */
          public void clearDisableReasonCounter() {
              Arrays.fill(mNetworkSeclectionDisableCounter, NETWORK_SELECTION_ENABLE);
          }
  
          /**
           * BSSID for connection to this network (through network selection procedure)
           */
          private String mNetworkSelectionBSSID;
  
          /**
           * get current network Selection BSSID
           * @return current network Selection BSSID
           */
          public String getNetworkSelectionBSSID() {
              return mNetworkSelectionBSSID;
          }
  
          /**
           * set network Selection BSSID
           * @param bssid The target BSSID for assocaition
           */
          public void setNetworkSelectionBSSID(String bssid) {
              mNetworkSelectionBSSID = bssid;
          }
  
          public void copy(NetworkSelectionStatus source) {
              mStatus = source.mStatus;
              mNetworkSelectionDisableReason = source.mNetworkSelectionDisableReason;
              for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
                      index++) {
                  mNetworkSeclectionDisableCounter[index] =
                          source.mNetworkSeclectionDisableCounter[index];
              }
              mTemporarilyDisabledTimestamp = source.mTemporarilyDisabledTimestamp;
              mNetworkSelectionBSSID = source.mNetworkSelectionBSSID;
              setSeenInLastQualifiedNetworkSelection(source.getSeenInLastQualifiedNetworkSelection());
              setCandidate(source.getCandidate());
              setCandidateScore(source.getCandidateScore());
              setConnectChoice(source.getConnectChoice());
              setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
              setHasEverConnected(source.getHasEverConnected());
              setNotRecommended(source.isNotRecommended());
          }
  
          public void writeToParcel(Parcel dest) {
              dest.writeInt(getNetworkSelectionStatus());
              dest.writeInt(getNetworkSelectionDisableReason());
              for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
                      index++) {
                  dest.writeInt(getDisableReasonCounter(index));
              }
              dest.writeLong(getDisableTime());
              dest.writeString(getNetworkSelectionBSSID());
              if (getConnectChoice() != null) {
                  dest.writeInt(CONNECT_CHOICE_EXISTS);
                  dest.writeString(getConnectChoice());
                  dest.writeLong(getConnectChoiceTimestamp());
              } else {
                  dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
              }
              dest.writeInt(getHasEverConnected() ? 1 : 0);
              dest.writeInt(isNotRecommended() ? 1 : 0);
          }
  
          public void readFromParcel(Parcel in) {
              setNetworkSelectionStatus(in.readInt());
              setNetworkSelectionDisableReason(in.readInt());
              for (int index = NETWORK_SELECTION_ENABLE; index < NETWORK_SELECTION_DISABLED_MAX;
                      index++) {
                  setDisableReasonCounter(index, in.readInt());
              }
              setDisableTime(in.readLong());
              setNetworkSelectionBSSID(in.readString());
              if (in.readInt() == CONNECT_CHOICE_EXISTS) {
                  setConnectChoice(in.readString());
                  setConnectChoiceTimestamp(in.readLong());
              } else {
                  setConnectChoice(null);
                  setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
              }
              setHasEverConnected(in.readInt() != 0);
              setNotRecommended(in.readInt() != 0);
          }
      }
  
      /**
       * @hide
       * network selection related member
       */
      private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
  
      /**
       * @hide
       * This class is intended to store extra failure reason information for the most recent
       * connection attempt, so that it may be surfaced to the settings UI
       */
      public static class RecentFailure {
  
          /**
           * No recent failure, or no specific reason given for the recent connection failure
           */
          public static final int NONE = 0;
          /**
           * Connection to this network recently failed due to Association Rejection Status 17
           * (AP is full)
           */
          public static final int STATUS_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
          /**
           * Association Rejection Status code (NONE for success/non-association-rejection-fail)
           */
          private int mAssociationStatus = NONE;
  
          /**
           * @param status the association status code for the recent failure
           */
          public void setAssociationStatus(int status) {
              mAssociationStatus = status;
          }
          /**
           * Sets the RecentFailure to NONE
           */
          public void clear() {
              mAssociationStatus = NONE;
          }
          /**
           * Get the recent failure code
           */
          public int getAssociationStatus() {
              return mAssociationStatus;
          }
      }
  
      /**
       * @hide
       * RecentFailure member
       */
      final public RecentFailure recentFailure = new RecentFailure();
  
      /**
       * @hide
       * @return network selection status
       */
      public NetworkSelectionStatus getNetworkSelectionStatus() {
          return mNetworkSelectionStatus;
      }
  
      /**
       * Set the network selection status
       * @hide
       */
      public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
          mNetworkSelectionStatus = status;
      }
  
      /**
       * @hide
       * Linked Configurations: represent the set of Wificonfigurations that are equivalent
       * regarding roaming and auto-joining.
       * The linked configuration may or may not have same SSID, and may or may not have same
       * credentials.
       * For instance, linked configurations will have same defaultGwMacAddress or same dhcp server.
       */
      public HashMap<String, Integer>  linkedConfigurations;
  
      public WifiConfiguration() {
          networkId = INVALID_NETWORK_ID;
          SSID = null;
          BSSID = null;
          FQDN = null;
          roamingConsortiumIds = new long[0];
          priority = 0;
          hiddenSSID = false;
          allowedKeyManagement = new BitSet();
          allowedProtocols = new BitSet();
          allowedAuthAlgorithms = new BitSet();
          allowedPairwiseCiphers = new BitSet();
          allowedGroupCiphers = new BitSet();
          allowedGroupManagementCiphers = new BitSet();
          allowedSuiteBCiphers = new BitSet();
          wepKeys = new String[4];
          for (int i = 0; i < wepKeys.length; i++) {
              wepKeys[i] = null;
          }
          enterpriseConfig = new WifiEnterpriseConfig();
          selfAdded = false;
          didSelfAdd = false;
          ephemeral = false;
          osu = false;
          trusted = true; // Networks are considered trusted by default.
          fromWifiNetworkSuggestion = false;
          fromWifiNetworkSpecifier = false;
          meteredHint = false;
          meteredOverride = METERED_OVERRIDE_NONE;
          useExternalScores = false;
          validatedInternetAccess = false;
          mIpConfiguration = new IpConfiguration();
          lastUpdateUid = -1;
          creatorUid = -1;
          shared = true;
          dtimInterval = 0;
          mRandomizedMacAddress = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
      }
  
      /**
       * Identify if this configuration represents a Passpoint network
       */
      public boolean isPasspoint() {
          return !TextUtils.isEmpty(FQDN)
                  && !TextUtils.isEmpty(providerFriendlyName)
                  && enterpriseConfig != null
                  && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
      }
  
      /**
       * Helper function, identify if a configuration is linked
       * @hide
       */
      public boolean isLinked(WifiConfiguration config) {
          if (config != null) {
              if (config.linkedConfigurations != null && linkedConfigurations != null) {
                  if (config.linkedConfigurations.get(configKey()) != null
                          && linkedConfigurations.get(config.configKey()) != null) {
                      return true;
                  }
              }
          }
          return  false;
      }
  
      /**
       * Helper function, idenfity if a configuration should be treated as an enterprise network
       * @hide
       */
      @UnsupportedAppUsage
      public boolean isEnterprise() {
          return (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
                  || allowedKeyManagement.get(KeyMgmt.IEEE8021X)
                  || allowedKeyManagement.get(KeyMgmt.SUITE_B_192))
                  && enterpriseConfig != null
                  && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE;
      }
  
      @Override
      public String toString() {
          StringBuilder sbuf = new StringBuilder();
          if (this.status == WifiConfiguration.Status.CURRENT) {
              sbuf.append("* ");
          } else if (this.status == WifiConfiguration.Status.DISABLED) {
              sbuf.append("- DSBLE ");
          }
          sbuf.append("ID: ").append(this.networkId).append(" SSID: ").append(this.SSID).
                  append(" PROVIDER-NAME: ").append(this.providerFriendlyName).
                  append(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN)
                  .append(" PRIO: ").append(this.priority)
                  .append(" HIDDEN: ").append(this.hiddenSSID)
                  .append(" PMF: ").append(this.requirePMF)
                  .append('\n');
  
  
          sbuf.append(" NetworkSelectionStatus ")
                  .append(mNetworkSelectionStatus.getNetworkStatusString() + "\n");
          if (mNetworkSelectionStatus.getNetworkSelectionDisableReason() > 0) {
              sbuf.append(" mNetworkSelectionDisableReason ")
                      .append(mNetworkSelectionStatus.getNetworkDisableReasonString() + "\n");
  
              for (int index = mNetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
                      index < mNetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; index++) {
                  if (mNetworkSelectionStatus.getDisableReasonCounter(index) != 0) {
                      sbuf.append(NetworkSelectionStatus.getNetworkDisableReasonString(index)
                              + " counter:" + mNetworkSelectionStatus.getDisableReasonCounter(index)
                              + "\n");
                  }
              }
          }
          if (mNetworkSelectionStatus.getConnectChoice() != null) {
              sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
              sbuf.append(" connect choice set time: ")
                      .append(TimeUtils.logTimeOfDay(
                              mNetworkSelectionStatus.getConnectChoiceTimestamp()));
          }
          sbuf.append(" hasEverConnected: ")
                  .append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
  
          if (this.numAssociation > 0) {
              sbuf.append(" numAssociation ").append(this.numAssociation).append("\n");
          }
          if (this.numNoInternetAccessReports > 0) {
              sbuf.append(" numNoInternetAccessReports ");
              sbuf.append(this.numNoInternetAccessReports).append("\n");
          }
          if (this.updateTime != null) {
              sbuf.append(" update ").append(this.updateTime).append("\n");
          }
          if (this.creationTime != null) {
              sbuf.append(" creation ").append(this.creationTime).append("\n");
          }
          if (this.didSelfAdd) sbuf.append(" didSelfAdd");
          if (this.selfAdded) sbuf.append(" selfAdded");
          if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
          if (this.ephemeral) sbuf.append(" ephemeral");
          if (this.osu) sbuf.append(" osu");
          if (this.trusted) sbuf.append(" trusted");
          if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
          if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
          if (this.meteredHint) sbuf.append(" meteredHint");
          if (this.useExternalScores) sbuf.append(" useExternalScores");
          if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess
                  || this.ephemeral || this.trusted || this.fromWifiNetworkSuggestion
                  || this.fromWifiNetworkSpecifier || this.meteredHint || this.useExternalScores) {
              sbuf.append("\n");
          }
          if (this.meteredOverride != METERED_OVERRIDE_NONE) {
              sbuf.append(" meteredOverride ").append(meteredOverride).append("\n");
          }
          sbuf.append(" macRandomizationSetting: ").append(macRandomizationSetting).append("\n");
          sbuf.append(" mRandomizedMacAddress: ").append(mRandomizedMacAddress).append("\n");
          sbuf.append(" KeyMgmt:");
          for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
              if (this.allowedKeyManagement.get(k)) {
                  sbuf.append(" ");
                  if (k < KeyMgmt.strings.length) {
                      sbuf.append(KeyMgmt.strings[k]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append(" Protocols:");
          for (int p = 0; p < this.allowedProtocols.size(); p++) {
              if (this.allowedProtocols.get(p)) {
                  sbuf.append(" ");
                  if (p < Protocol.strings.length) {
                      sbuf.append(Protocol.strings[p]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n');
          sbuf.append(" AuthAlgorithms:");
          for (int a = 0; a < this.allowedAuthAlgorithms.size(); a++) {
              if (this.allowedAuthAlgorithms.get(a)) {
                  sbuf.append(" ");
                  if (a < AuthAlgorithm.strings.length) {
                      sbuf.append(AuthAlgorithm.strings[a]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n');
          sbuf.append(" PairwiseCiphers:");
          for (int pc = 0; pc < this.allowedPairwiseCiphers.size(); pc++) {
              if (this.allowedPairwiseCiphers.get(pc)) {
                  sbuf.append(" ");
                  if (pc < PairwiseCipher.strings.length) {
                      sbuf.append(PairwiseCipher.strings[pc]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n');
          sbuf.append(" GroupCiphers:");
          for (int gc = 0; gc < this.allowedGroupCiphers.size(); gc++) {
              if (this.allowedGroupCiphers.get(gc)) {
                  sbuf.append(" ");
                  if (gc < GroupCipher.strings.length) {
                      sbuf.append(GroupCipher.strings[gc]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n');
          sbuf.append(" GroupMgmtCiphers:");
          for (int gmc = 0; gmc < this.allowedGroupManagementCiphers.size(); gmc++) {
              if (this.allowedGroupManagementCiphers.get(gmc)) {
                  sbuf.append(" ");
                  if (gmc < GroupMgmtCipher.strings.length) {
                      sbuf.append(GroupMgmtCipher.strings[gmc]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n');
          sbuf.append(" SuiteBCiphers:");
          for (int sbc = 0; sbc < this.allowedSuiteBCiphers.size(); sbc++) {
              if (this.allowedSuiteBCiphers.get(sbc)) {
                  sbuf.append(" ");
                  if (sbc < SuiteBCipher.strings.length) {
                      sbuf.append(SuiteBCipher.strings[sbc]);
                  } else {
                      sbuf.append("??");
                  }
              }
          }
          sbuf.append('\n').append(" PSK/SAE: ");
          if (this.preSharedKey != null) {
              sbuf.append('*');
          }
  
          sbuf.append("\nEnterprise config:\n");
          sbuf.append(enterpriseConfig);
  
          sbuf.append("IP config:\n");
          sbuf.append(mIpConfiguration.toString());
  
          if (mNetworkSelectionStatus.getNetworkSelectionBSSID() != null) {
              sbuf.append(" networkSelectionBSSID="
                      + mNetworkSelectionStatus.getNetworkSelectionBSSID());
          }
          long now_ms = SystemClock.elapsedRealtime();
          if (mNetworkSelectionStatus.getDisableTime() != NetworkSelectionStatus
                  .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP) {
              sbuf.append('\n');
              long diff = now_ms - mNetworkSelectionStatus.getDisableTime();
              if (diff <= 0) {
                  sbuf.append(" blackListed since <incorrect>");
              } else {
                  sbuf.append(" blackListed: ").append(Long.toString(diff / 1000)).append("sec ");
              }
          }
          if (creatorUid != 0) sbuf.append(" cuid=" + creatorUid);
          if (creatorName != null) sbuf.append(" cname=" + creatorName);
          if (lastUpdateUid != 0) sbuf.append(" luid=" + lastUpdateUid);
          if (lastUpdateName != null) sbuf.append(" lname=" + lastUpdateName);
          if (updateIdentifier != null) sbuf.append(" updateIdentifier=" + updateIdentifier);
          sbuf.append(" lcuid=" + lastConnectUid);
          sbuf.append(" userApproved=" + userApprovedAsString(userApproved));
          sbuf.append(" noInternetAccessExpected=" + noInternetAccessExpected);
          sbuf.append(" ");
  
          if (this.lastConnected != 0) {
              sbuf.append('\n');
              sbuf.append("lastConnected: ").append(TimeUtils.logTimeOfDay(this.lastConnected));
              sbuf.append(" ");
          }
          sbuf.append('\n');
          if (this.linkedConfigurations != null) {
              for (String key : this.linkedConfigurations.keySet()) {
                  sbuf.append(" linked: ").append(key);
                  sbuf.append('\n');
              }
          }
          sbuf.append("recentFailure: ").append("Association Rejection code: ")
                  .append(recentFailure.getAssociationStatus()).append("\n");
          return sbuf.toString();
      }
  
      /** {@hide} */
      @UnsupportedAppUsage
      public String getPrintableSsid() {
          if (SSID == null) return "";
          final int length = SSID.length();
          if (length > 2 && (SSID.charAt(0) == '"') && SSID.charAt(length - 1) == '"') {
              return SSID.substring(1, length - 1);
          }
  
          /** The ascii-encoded string format is P"<ascii-encoded-string>"
           * The decoding is implemented in the supplicant for a newly configured
           * network.
           */
          if (length > 3 && (SSID.charAt(0) == 'P') && (SSID.charAt(1) == '"') &&
                  (SSID.charAt(length-1) == '"')) {
              WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(
                      SSID.substring(2, length - 1));
              return wifiSsid.toString();
          }
          return SSID;
      }
  
      /** @hide **/
      public static String userApprovedAsString(int userApproved) {
          switch (userApproved) {
              case USER_APPROVED:
                  return "USER_APPROVED";
              case USER_BANNED:
                  return "USER_BANNED";
              case USER_UNSPECIFIED:
                  return "USER_UNSPECIFIED";
              default:
                  return "INVALID";
          }
      }
  
      /**
       * Get an identifier for associating credentials with this config
       * @param current configuration contains values for additional fields
       *                that are not part of this configuration. Used
       *                when a config with some fields is passed by an application.
       * @throws IllegalStateException if config is invalid for key id generation
       * @hide
       */
      public String getKeyIdForCredentials(WifiConfiguration current) {
          String keyMgmt = "";
  
          try {
              // Get current config details for fields that are not initialized
              if (TextUtils.isEmpty(SSID)) SSID = current.SSID;
              if (allowedKeyManagement.cardinality() == 0) {
                  allowedKeyManagement = current.allowedKeyManagement;
              }
              if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
                  keyMgmt += KeyMgmt.strings[KeyMgmt.WPA_EAP];
              }
              if (allowedKeyManagement.get(KeyMgmt.OSEN)) {
                  keyMgmt += KeyMgmt.strings[KeyMgmt.OSEN];
              }
              if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
                  keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X];
              }
              if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
                  keyMgmt += KeyMgmt.strings[KeyMgmt.SUITE_B_192];
              }
  
              if (TextUtils.isEmpty(keyMgmt)) {
                  throw new IllegalStateException("Not an EAP network");
              }
  
              String keyId = trimStringForKeyId(SSID) + "_" + keyMgmt + "_"
                      + trimStringForKeyId(enterpriseConfig.getKeyId(current != null
                      ? current.enterpriseConfig : null));
  
              if (!fromWifiNetworkSuggestion) {
                  return keyId;
              }
              return keyId + "_" + trimStringForKeyId(BSSID) + "_" + trimStringForKeyId(creatorName);
          } catch (NullPointerException e) {
              throw new IllegalStateException("Invalid config details");
          }
      }
  
      private String trimStringForKeyId(String string) {
          if (string == null) {
              return "";
          }
          // Remove quotes and spaces
          return string.replace("\"", "").replace(" ", "");
      }
  
      private static BitSet readBitSet(Parcel src) {
          int cardinality = src.readInt();
  
          BitSet set = new BitSet();
          for (int i = 0; i < cardinality; i++) {
              set.set(src.readInt());
          }
  
          return set;
      }
  
      private static void writeBitSet(Parcel dest, BitSet set) {
          int nextSetBit = -1;
  
          dest.writeInt(set.cardinality());
  
          while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
              dest.writeInt(nextSetBit);
          }
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public int getAuthType() {
          if (allowedKeyManagement.cardinality() > 1) {
              throw new IllegalStateException("More than one auth type set");
          }
          if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
              return KeyMgmt.WPA_PSK;
          } else if (allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
              return KeyMgmt.WPA2_PSK;
          } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)) {
              return KeyMgmt.WPA_EAP;
          } else if (allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
              return KeyMgmt.IEEE8021X;
          } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
              return KeyMgmt.SAE;
          } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
              return KeyMgmt.OWE;
          } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
              return KeyMgmt.SUITE_B_192;
          }
          return KeyMgmt.NONE;
      }
  
      /* @hide
       * Cache the config key, this seems useful as a speed up since a lot of
       * lookups in the config store are done and based on this key.
       */
      String mCachedConfigKey;
  
      /** @hide
       *  return the string used to calculate the hash in WifiConfigStore
       *  and uniquely identify this WifiConfiguration
       */
      public String configKey(boolean allowCached) {
          String key;
          if (allowCached && mCachedConfigKey != null) {
              key = mCachedConfigKey;
          } else if (providerFriendlyName != null) {
              key = FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP];
              if (!shared) {
                  key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
              }
          } else {
              key = getSsidAndSecurityTypeString();
              if (!shared) {
                  key += "-" + Integer.toString(UserHandle.getUserId(creatorUid));
              }
              mCachedConfigKey = key;
          }
          return key;
      }
  
      /** @hide
       *  return the SSID + security type in String format.
       */
      public String getSsidAndSecurityTypeString() {
          String key;
          if (allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
              key = SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
          } else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
                  || allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
              key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
          } else if (wepKeys[0] != null) {
              key = SSID + "WEP";
          } else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
              key = SSID + KeyMgmt.strings[KeyMgmt.OWE];
          } else if (allowedKeyManagement.get(KeyMgmt.SAE)) {
              key = SSID + KeyMgmt.strings[KeyMgmt.SAE];
          } else if (allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) {
              key = SSID + KeyMgmt.strings[KeyMgmt.SUITE_B_192];
          } else {
              key = SSID + KeyMgmt.strings[KeyMgmt.NONE];
          }
          return key;
      }
  
      /** @hide
       * get configKey, force calculating the config string
       */
      public String configKey() {
          return configKey(false);
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public IpConfiguration getIpConfiguration() {
          return mIpConfiguration;
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setIpConfiguration(IpConfiguration ipConfiguration) {
          if (ipConfiguration == null) ipConfiguration = new IpConfiguration();
          mIpConfiguration = ipConfiguration;
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public StaticIpConfiguration getStaticIpConfiguration() {
          return mIpConfiguration.getStaticIpConfiguration();
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setStaticIpConfiguration(StaticIpConfiguration staticIpConfiguration) {
          mIpConfiguration.setStaticIpConfiguration(staticIpConfiguration);
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public IpConfiguration.IpAssignment getIpAssignment() {
          return mIpConfiguration.ipAssignment;
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setIpAssignment(IpConfiguration.IpAssignment ipAssignment) {
          mIpConfiguration.ipAssignment = ipAssignment;
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public IpConfiguration.ProxySettings getProxySettings() {
          return mIpConfiguration.proxySettings;
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setProxySettings(IpConfiguration.ProxySettings proxySettings) {
          mIpConfiguration.proxySettings = proxySettings;
      }
  
      /**
       * Returns the HTTP proxy used by this object.
       * @return a {@link ProxyInfo httpProxy} representing the proxy specified by this
       *                  WifiConfiguration, or {@code null} if no proxy is specified.
       */
      public ProxyInfo getHttpProxy() {
          if (mIpConfiguration.proxySettings == IpConfiguration.ProxySettings.NONE) {
              return null;
          }
          return new ProxyInfo(mIpConfiguration.httpProxy);
      }
  
      /**
       * Set the {@link ProxyInfo} for this WifiConfiguration. This method should only be used by a
       * device owner or profile owner. When other apps attempt to save a {@link WifiConfiguration}
       * with modified proxy settings, the methods {@link WifiManager#addNetwork} and
       * {@link WifiManager#updateNetwork} fail and return {@code -1}.
       *
       * @param httpProxy {@link ProxyInfo} representing the httpProxy to be used by this
       *                  WifiConfiguration. Setting this to {@code null} will explicitly set no
       *                  proxy, removing any proxy that was previously set.
       * @exception IllegalArgumentException for invalid httpProxy
       */
      public void setHttpProxy(ProxyInfo httpProxy) {
          if (httpProxy == null) {
              mIpConfiguration.setProxySettings(IpConfiguration.ProxySettings.NONE);
              mIpConfiguration.setHttpProxy(null);
              return;
          }
          ProxyInfo httpProxyCopy;
          ProxySettings proxySettingCopy;
          if (!Uri.EMPTY.equals(httpProxy.getPacFileUrl())) {
              proxySettingCopy = IpConfiguration.ProxySettings.PAC;
              // Construct a new PAC URL Proxy
              httpProxyCopy = new ProxyInfo(httpProxy.getPacFileUrl(), httpProxy.getPort());
          } else {
              proxySettingCopy = IpConfiguration.ProxySettings.STATIC;
              // Construct a new HTTP Proxy
              httpProxyCopy = new ProxyInfo(httpProxy.getHost(), httpProxy.getPort(),
                      httpProxy.getExclusionListAsString());
          }
          if (!httpProxyCopy.isValid()) {
              throw new IllegalArgumentException("Invalid ProxyInfo: " + httpProxyCopy.toString());
          }
          mIpConfiguration.setProxySettings(proxySettingCopy);
          mIpConfiguration.setHttpProxy(httpProxyCopy);
      }
  
      /** @hide */
      @UnsupportedAppUsage
      public void setProxy(ProxySettings settings, ProxyInfo proxy) {
          mIpConfiguration.proxySettings = settings;
          mIpConfiguration.httpProxy = proxy;
      }
  
      /** Implement the Parcelable interface {@hide} */
      public int describeContents() {
          return 0;
      }
  
      /** @hide */
      public void setPasspointManagementObjectTree(String passpointManagementObjectTree) {
          mPasspointManagementObjectTree = passpointManagementObjectTree;
      }
  
      /** @hide */
      public String getMoTree() {
          return mPasspointManagementObjectTree;
      }
  
      /** copy constructor {@hide} */
      @UnsupportedAppUsage
      public WifiConfiguration(WifiConfiguration source) {
          if (source != null) {
              networkId = source.networkId;
              status = source.status;
              SSID = source.SSID;
              BSSID = source.BSSID;
              FQDN = source.FQDN;
              roamingConsortiumIds = source.roamingConsortiumIds.clone();
              providerFriendlyName = source.providerFriendlyName;
              isHomeProviderNetwork = source.isHomeProviderNetwork;
              preSharedKey = source.preSharedKey;
  
              mNetworkSelectionStatus.copy(source.getNetworkSelectionStatus());
              apBand = source.apBand;
              apChannel = source.apChannel;
  
              wepKeys = new String[4];
              for (int i = 0; i < wepKeys.length; i++) {
                  wepKeys[i] = source.wepKeys[i];
              }
  
              wepTxKeyIndex = source.wepTxKeyIndex;
              priority = source.priority;
              hiddenSSID = source.hiddenSSID;
              allowedKeyManagement   = (BitSet) source.allowedKeyManagement.clone();
              allowedProtocols       = (BitSet) source.allowedProtocols.clone();
              allowedAuthAlgorithms  = (BitSet) source.allowedAuthAlgorithms.clone();
              allowedPairwiseCiphers = (BitSet) source.allowedPairwiseCiphers.clone();
              allowedGroupCiphers    = (BitSet) source.allowedGroupCiphers.clone();
              allowedGroupManagementCiphers = (BitSet) source.allowedGroupManagementCiphers.clone();
              allowedSuiteBCiphers    = (BitSet) source.allowedSuiteBCiphers.clone();
              enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig);
  
              defaultGwMacAddress = source.defaultGwMacAddress;
  
              mIpConfiguration = new IpConfiguration(source.mIpConfiguration);
  
              if ((source.linkedConfigurations != null)
                      && (source.linkedConfigurations.size() > 0)) {
                  linkedConfigurations = new HashMap<String, Integer>();
                  linkedConfigurations.putAll(source.linkedConfigurations);
              }
              mCachedConfigKey = null; //force null configKey
              selfAdded = source.selfAdded;
              validatedInternetAccess = source.validatedInternetAccess;
              isLegacyPasspointConfig = source.isLegacyPasspointConfig;
              ephemeral = source.ephemeral;
              osu = source.osu;
              trusted = source.trusted;
              fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
              fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
              meteredHint = source.meteredHint;
              meteredOverride = source.meteredOverride;
              useExternalScores = source.useExternalScores;
  
              didSelfAdd = source.didSelfAdd;
              lastConnectUid = source.lastConnectUid;
              lastUpdateUid = source.lastUpdateUid;
              creatorUid = source.creatorUid;
              creatorName = source.creatorName;
              lastUpdateName = source.lastUpdateName;
              peerWifiConfiguration = source.peerWifiConfiguration;
  
              lastConnected = source.lastConnected;
              lastDisconnected = source.lastDisconnected;
              numScorerOverride = source.numScorerOverride;
              numScorerOverrideAndSwitchedNetwork = source.numScorerOverrideAndSwitchedNetwork;
              numAssociation = source.numAssociation;
              userApproved = source.userApproved;
              numNoInternetAccessReports = source.numNoInternetAccessReports;
              noInternetAccessExpected = source.noInternetAccessExpected;
              creationTime = source.creationTime;
              updateTime = source.updateTime;
              shared = source.shared;
              recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
              mRandomizedMacAddress = source.mRandomizedMacAddress;
              macRandomizationSetting = source.macRandomizationSetting;
              requirePMF = source.requirePMF;
              updateIdentifier = source.updateIdentifier;
          }
      }
  
      /** Implement the Parcelable interface {@hide} */
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeInt(networkId);
          dest.writeInt(status);
          mNetworkSelectionStatus.writeToParcel(dest);
          dest.writeString(SSID);
          dest.writeString(BSSID);
          dest.writeInt(apBand);
          dest.writeInt(apChannel);
          dest.writeString(FQDN);
          dest.writeString(providerFriendlyName);
          dest.writeInt(isHomeProviderNetwork ? 1 : 0);
          dest.writeInt(roamingConsortiumIds.length);
          for (long roamingConsortiumId : roamingConsortiumIds) {
              dest.writeLong(roamingConsortiumId);
          }
          dest.writeString(preSharedKey);
          for (String wepKey : wepKeys) {
              dest.writeString(wepKey);
          }
          dest.writeInt(wepTxKeyIndex);
          dest.writeInt(priority);
          dest.writeInt(hiddenSSID ? 1 : 0);
          dest.writeInt(requirePMF ? 1 : 0);
          dest.writeString(updateIdentifier);
  
          writeBitSet(dest, allowedKeyManagement);
          writeBitSet(dest, allowedProtocols);
          writeBitSet(dest, allowedAuthAlgorithms);
          writeBitSet(dest, allowedPairwiseCiphers);
          writeBitSet(dest, allowedGroupCiphers);
          writeBitSet(dest, allowedGroupManagementCiphers);
          writeBitSet(dest, allowedSuiteBCiphers);
  
          dest.writeParcelable(enterpriseConfig, flags);
  
          dest.writeParcelable(mIpConfiguration, flags);
          dest.writeString(dhcpServer);
          dest.writeString(defaultGwMacAddress);
          dest.writeInt(selfAdded ? 1 : 0);
          dest.writeInt(didSelfAdd ? 1 : 0);
          dest.writeInt(validatedInternetAccess ? 1 : 0);
          dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
          dest.writeInt(ephemeral ? 1 : 0);
          dest.writeInt(trusted ? 1 : 0);
          dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
          dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
          dest.writeInt(meteredHint ? 1 : 0);
          dest.writeInt(meteredOverride);
          dest.writeInt(useExternalScores ? 1 : 0);
          dest.writeInt(creatorUid);
          dest.writeInt(lastConnectUid);
          dest.writeInt(lastUpdateUid);
          dest.writeString(creatorName);
          dest.writeString(lastUpdateName);
          dest.writeInt(numScorerOverride);
          dest.writeInt(numScorerOverrideAndSwitchedNetwork);
          dest.writeInt(numAssociation);
          dest.writeInt(userApproved);
          dest.writeInt(numNoInternetAccessReports);
          dest.writeInt(noInternetAccessExpected ? 1 : 0);
          dest.writeInt(shared ? 1 : 0);
          dest.writeString(mPasspointManagementObjectTree);
          dest.writeInt(recentFailure.getAssociationStatus());
          dest.writeParcelable(mRandomizedMacAddress, flags);
          dest.writeInt(macRandomizationSetting);
          dest.writeInt(osu ? 1 : 0);
      }
  
      /** Implement the Parcelable interface {@hide} */
      @UnsupportedAppUsage
      public static final @android.annotation.NonNull Creator<WifiConfiguration> CREATOR =
          new Creator<WifiConfiguration>() {
              public WifiConfiguration createFromParcel(Parcel in) {
                  WifiConfiguration config = new WifiConfiguration();
                  config.networkId = in.readInt();
                  config.status = in.readInt();
                  config.mNetworkSelectionStatus.readFromParcel(in);
                  config.SSID = in.readString();
                  config.BSSID = in.readString();
                  config.apBand = in.readInt();
                  config.apChannel = in.readInt();
                  config.FQDN = in.readString();
                  config.providerFriendlyName = in.readString();
                  config.isHomeProviderNetwork = in.readInt() != 0;
                  int numRoamingConsortiumIds = in.readInt();
                  config.roamingConsortiumIds = new long[numRoamingConsortiumIds];
                  for (int i = 0; i < numRoamingConsortiumIds; i++) {
                      config.roamingConsortiumIds[i] = in.readLong();
                  }
                  config.preSharedKey = in.readString();
                  for (int i = 0; i < config.wepKeys.length; i++) {
                      config.wepKeys[i] = in.readString();
                  }
                  config.wepTxKeyIndex = in.readInt();
                  config.priority = in.readInt();
                  config.hiddenSSID = in.readInt() != 0;
                  config.requirePMF = in.readInt() != 0;
                  config.updateIdentifier = in.readString();
  
                  config.allowedKeyManagement   = readBitSet(in);
                  config.allowedProtocols       = readBitSet(in);
                  config.allowedAuthAlgorithms  = readBitSet(in);
                  config.allowedPairwiseCiphers = readBitSet(in);
                  config.allowedGroupCiphers    = readBitSet(in);
                  config.allowedGroupManagementCiphers = readBitSet(in);
                  config.allowedSuiteBCiphers   = readBitSet(in);
  
                  config.enterpriseConfig = in.readParcelable(null);
                  config.setIpConfiguration(in.readParcelable(null));
                  config.dhcpServer = in.readString();
                  config.defaultGwMacAddress = in.readString();
                  config.selfAdded = in.readInt() != 0;
                  config.didSelfAdd = in.readInt() != 0;
                  config.validatedInternetAccess = in.readInt() != 0;
                  config.isLegacyPasspointConfig = in.readInt() != 0;
                  config.ephemeral = in.readInt() != 0;
                  config.trusted = in.readInt() != 0;
                  config.fromWifiNetworkSuggestion =  in.readInt() != 0;
                  config.fromWifiNetworkSpecifier =  in.readInt() != 0;
                  config.meteredHint = in.readInt() != 0;
                  config.meteredOverride = in.readInt();
                  config.useExternalScores = in.readInt() != 0;
                  config.creatorUid = in.readInt();
                  config.lastConnectUid = in.readInt();
                  config.lastUpdateUid = in.readInt();
                  config.creatorName = in.readString();
                  config.lastUpdateName = in.readString();
                  config.numScorerOverride = in.readInt();
                  config.numScorerOverrideAndSwitchedNetwork = in.readInt();
                  config.numAssociation = in.readInt();
                  config.userApproved = in.readInt();
                  config.numNoInternetAccessReports = in.readInt();
                  config.noInternetAccessExpected = in.readInt() != 0;
                  config.shared = in.readInt() != 0;
                  config.mPasspointManagementObjectTree = in.readString();
                  config.recentFailure.setAssociationStatus(in.readInt());
                  config.mRandomizedMacAddress = in.readParcelable(null);
                  config.macRandomizationSetting = in.readInt();
                  config.osu = in.readInt() != 0;
                  return config;
              }
  
              public WifiConfiguration[] newArray(int size) {
                  return new WifiConfiguration[size];
              }
          };
  
      /**
       * Serializes the object for backup
       * @hide
       */
      public byte[] getBytesForBackup() throws IOException {
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          DataOutputStream out = new DataOutputStream(baos);
  
          out.writeInt(BACKUP_VERSION);
          BackupUtils.writeString(out, SSID);
          out.writeInt(apBand);
          out.writeInt(apChannel);
          BackupUtils.writeString(out, preSharedKey);
          out.writeInt(getAuthType());
          out.writeBoolean(hiddenSSID);
          return baos.toByteArray();
      }
  
      /**
       * Deserializes a byte array into the WiFiConfiguration Object
       * @hide
       */
      public static WifiConfiguration getWifiConfigFromBackup(DataInputStream in) throws IOException,
              BackupUtils.BadVersionException {
          WifiConfiguration config = new WifiConfiguration();
          int version = in.readInt();
          if (version < 1 || version > BACKUP_VERSION) {
              throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
          }
  
          if (version == 1) return null; // Version 1 is a bad dataset.
  
          config.SSID = BackupUtils.readString(in);
          config.apBand = in.readInt();
          config.apChannel = in.readInt();
          config.preSharedKey = BackupUtils.readString(in);
          config.allowedKeyManagement.set(in.readInt());
          if (version >= 3) {
              config.hiddenSSID = in.readBoolean();
          }
          return config;
      }
  }
  