/* * 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.SystemApi; import android.net.IpConfiguration; import android.net.IpConfiguration.ProxySettings; import android.net.IpConfiguration.IpAssignment; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.annotation.SystemApi; import java.util.HashMap; import java.util.BitSet; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /** * A class representing a configured Wi-Fi network, including the * security configuration. */ public class WifiConfiguration implements Parcelable { private static final String TAG = "WifiConfiguration"; /** {@hide} */ public static final String ssidVarName = "ssid"; /** {@hide} */ public static final String bssidVarName = "bssid"; /** {@hide} */ public static final String pskVarName = "psk"; /** {@hide} */ public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" }; /** {@hide} */ 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; /** * 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 */ public static final int WPA2_PSK = 4; public static final String varName = "key_mgmt"; public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X", "WPA2_PSK" }; } /** * Recognized security protocols. */ public static class Protocol { private Protocol() { } /** WPA/IEEE 802.11i/D3.0 */ public static final int WPA = 0; /** WPA2/IEEE 802.11i */ public static final int RSN = 1; public static final String varName = "proto"; public static final String[] strings = { "WPA", "RSN" }; } /** * 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) */ 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] */ 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; public static final String varName = "pairwise"; public static final String[] strings = { "NONE", "TKIP", "CCMP" }; } /** * Recognized group ciphers. *
     * 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)
     * 
*/ public static class GroupCipher { private GroupCipher() { } /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) */ public static final int WEP40 = 0; /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key */ 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; public static final String varName = "group"; public static final String[] strings = { "WEP40", "WEP104", "TKIP", "CCMP" }; } /** 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" }; } /** @hide */ public static final int DISABLED_UNKNOWN_REASON = 0; /** @hide */ public static final int DISABLED_DNS_FAILURE = 1; /** @hide */ public static final int DISABLED_DHCP_FAILURE = 2; /** @hide */ public static final int DISABLED_AUTH_FAILURE = 3; /** @hide */ public static final int DISABLED_ASSOCIATION_REJECT = 4; /** @hide */ public static final int DISABLED_BY_WIFI_MANAGER = 5; /** * 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; /** * The current status of this network configuration entry. * @see Status */ public int status; /** * The configuration needs to be written to networkHistory.txt * @hide */ public boolean dirty; /** * The code referring to a reason for disabling the network * Valid when {@link #status} == Status.DISABLED * @hide */ public int disableReason; /** * The network's SSID. Can either be an ASCII 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., * XX:XX:XX:XX:XX:XX where each X is a hex digit. */ public String BSSID; /** * Fully qualified domain name (FQDN) of AAA server or RADIUS server * e.g. {@code "mail.example.com"}. */ public String FQDN; /** * Network access identifier (NAI) realm, for Passpoint credential. * e.g. {@code "myhost.example.com"}. * @hide */ public String naiRealm; /** * Pre-shared key for use with WPA-PSK. *

* 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; /** * Up to four WEP keys. Either an ASCII string enclosed in double * quotation marks (e.g., {@code "abcdef"} or a string * of hex digits (e.g., {@code 0102030405}). *

* 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. */ public String[] wepKeys; /** Default WEP key index, ranging from 0 to 3. */ public int wepTxKeyIndex; /** * Priority determines the preference given to a network by {@code wpa_supplicant} * when choosing an access point with which to associate. */ 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. */ public BitSet allowedKeyManagement; /** * The set of security protocols supported by this configuration. * See {@link Protocol} for descriptions of the values. * Defaults to WPA RSN. */ public BitSet allowedProtocols; /** * The set of authentication protocols supported by this configuration. * See {@link AuthAlgorithm} for descriptions of the values. * Defaults to automatic selection. */ 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. */ 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. */ public BitSet allowedGroupCiphers; /** * The enterprise configuration details specifying the EAP method, * certificates and other settings associated with the EAP. */ public WifiEnterpriseConfig enterpriseConfig; /** * @hide */ private IpConfiguration mIpConfiguration; /** * @hide * dhcp server MAC address if known */ public String dhcpServer; /** * @hide * default Gateway MAC address if known */ public String defaultGwMacAddress; /** * @hide * last failure */ public String lastFailure; /** * @hide * last time we connected, this configuration had validated internet access */ public boolean validatedInternetAccess; /** * @hide * Uid of app creating the configuration */ @SystemApi public int creatorUid; /** * @hide * Uid of last app issuing a connection related command */ public int lastConnectUid; /** * @hide * Uid of last app modifying the configuration */ @SystemApi public int lastUpdateUid; /** * @hide * Uid used by autoJoin */ public String autoJoinBSSID; /** * @hide * BSSID list on which this configuration was seen. * TODO: prevent this list to grow infinitely, age-out the results */ public HashMap scanResultCache; /** 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 **/ public static int INVALID_RSSI = -127; /** @hide **/ public static int UNWANTED_BLACKLIST_SOFT_RSSI_24 = -80; /** @hide **/ public static int UNWANTED_BLACKLIST_SOFT_RSSI_5 = -70; /** @hide **/ public static int GOOD_RSSI_24 = -65; /** @hide **/ public static int LOW_RSSI_24 = -77; /** @hide **/ public static int BAD_RSSI_24 = -87; /** @hide **/ public static int GOOD_RSSI_5 = -60; /** @hide **/ public static int LOW_RSSI_5 = -72; /** @hide **/ public static int BAD_RSSI_5 = -82; /** @hide **/ public static int UNWANTED_BLACKLIST_SOFT_BUMP = 4; /** @hide **/ public static int UNWANTED_BLACKLIST_HARD_BUMP = 8; /** @hide **/ public static int UNBLACKLIST_THRESHOLD_24_SOFT = -77; /** @hide **/ public static int UNBLACKLIST_THRESHOLD_24_HARD = -68; /** @hide **/ public static int UNBLACKLIST_THRESHOLD_5_SOFT = -63; /** @hide **/ public static int UNBLACKLIST_THRESHOLD_5_HARD = -56; /** @hide **/ public static int INITIAL_AUTO_JOIN_ATTEMPT_MIN_24 = -80; /** @hide **/ public static int INITIAL_AUTO_JOIN_ATTEMPT_MIN_5 = -70; /** @hide * 5GHz band is prefered low over 2.4 if the 5GHz RSSI is higher than this threshold */ public static int A_BAND_PREFERENCE_RSSI_THRESHOLD = -65; /** @hide * 5GHz band is penalized if the 5GHz RSSI is lower than this threshold **/ public static int G_BAND_PREFERENCE_RSSI_THRESHOLD = -75; /** @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 int HOME_NETWORK_RSSI_BOOST = 5; /** @hide * RSSI boost for configuration which use autoJoinUseAggressiveJoinAttemptThreshold * To be more aggressive when initially attempting to auto join */ public static int MAX_INITIAL_AUTO_JOIN_RSSI_BOOST = 8; /** * @hide * A summary of the RSSI and Band status for that configuration * This is used as a temporary value by the auto-join controller */ public final class Visibility { public int rssi5; // strongest 5GHz RSSI public int rssi24; // strongest 2.4GHz RSSI public int num5; // number of BSSIDs on 5GHz public int num24; // number of BSSIDs on 2.4GHz public long age5; // timestamp of the strongest 5GHz BSSID (last time it was seen) public long age24; // timestamp of the strongest 2.4GHz BSSID (last time it was seen) public String BSSID24; public String BSSID5; public int score; // Debug only, indicate last score used for autojoin/cell-handover public int currentNetworkBoost; // Debug only, indicate boost applied to RSSI if current public int bandPreferenceBoost; // Debug only, indicate boost applied to RSSI if current public int lastChoiceBoost; // Debug only, indicate last choice applied to this configuration public String lastChoiceConfig; // Debug only, indicate last choice applied to this configuration public Visibility() { rssi5 = INVALID_RSSI; rssi24 = INVALID_RSSI; } public Visibility(Visibility source) { rssi5 = source.rssi5; rssi24 = source.rssi24; age24 = source.age24; age5 = source.age5; num24 = source.num24; num5 = source.num5; BSSID5 = source.BSSID5; BSSID24 = source.BSSID24; } @Override public String toString() { StringBuilder sbuf = new StringBuilder(); sbuf.append("["); if (rssi24 > INVALID_RSSI) { sbuf.append(Integer.toString(rssi24)); sbuf.append(","); sbuf.append(Integer.toString(num24)); if (BSSID24 != null) sbuf.append(",").append(BSSID24); } sbuf.append("; "); if (rssi5 > INVALID_RSSI) { sbuf.append(Integer.toString(rssi5)); sbuf.append(","); sbuf.append(Integer.toString(num5)); if (BSSID5 != null) sbuf.append(",").append(BSSID5); } if (score != 0) { sbuf.append("; ").append(score); sbuf.append(", ").append(currentNetworkBoost); sbuf.append(", ").append(bandPreferenceBoost); if (lastChoiceConfig != null) { sbuf.append(", ").append(lastChoiceBoost); sbuf.append(", ").append(lastChoiceConfig); } } sbuf.append("]"); return sbuf.toString(); } } /** @hide * Cache the visibility status of this configuration. * Visibility can change at any time depending on scan results availability. * Owner of the WifiConfiguration is responsible to set this field based on * recent scan results. ***/ public Visibility visibility; /** @hide * calculate and set Visibility for that configuration. * * age in milliseconds: we will consider only ScanResults that are more recent, * i.e. younger. ***/ public Visibility setVisibility(long age) { if (scanResultCache == null) { visibility = null; return null; } Visibility status = new Visibility(); long now_ms = System.currentTimeMillis(); for(ScanResult result : scanResultCache.values()) { if (result.seen == 0) continue; if (result.is5GHz()) { //strictly speaking: [4915, 5825] //number of known BSSID on 5GHz band status.num5 = status.num5 + 1; } else if (result.is24GHz()) { //strictly speaking: [2412, 2482] //number of known BSSID on 2.4Ghz band status.num24 = status.num24 + 1; } if ((now_ms - result.seen) > age) continue; if (result.is5GHz()) { if (result.level > status.rssi5) { status.rssi5 = result.level; status.age5 = result.seen; status.BSSID5 = result.BSSID; } } else if (result.is24GHz()) { if (result.level > status.rssi24) { status.rssi24 = result.level; status.age24 = result.seen; status.BSSID24 = result.BSSID; } } } visibility = status; return status; } /** @hide */ public static final int AUTO_JOIN_ENABLED = 0; /** * if this is set, the WifiConfiguration cannot use linkages so as to bump * it's relative priority. * - status between and 128 indicate various level of blacklisting depending * on the severity or frequency of the connection error * - deleted status indicates that the user is deleting the configuration, and so * although it may have been self added we will not re-self-add it, ignore it, * not return it to applications, and not connect to it * */ /** @hide * network was temporary disabled due to bad connection, most likely due * to weak RSSI */ public static final int AUTO_JOIN_TEMPORARY_DISABLED = 1; /** @hide * network was temporary disabled due to bad connection, which cant be attributed * to weak RSSI */ public static final int AUTO_JOIN_TEMPORARY_DISABLED_LINK_ERRORS = 32; /** @hide */ public static final int AUTO_JOIN_TEMPORARY_DISABLED_AT_SUPPLICANT = 64; /** @hide */ public static final int AUTO_JOIN_DISABLED_ON_AUTH_FAILURE = 128; /** @hide */ public static final int AUTO_JOIN_DISABLED_NO_CREDENTIALS = 160; /** @hide */ public static final int AUTO_JOIN_DISABLED_USER_ACTION = 161; /** @hide */ public static final int AUTO_JOIN_DELETED = 200; /** * @hide */ public int autoJoinStatus; /** * @hide * Number of connection failures */ public int numConnectionFailures; /** * @hide * Number of IP config failures */ public int numIpConfigFailures; /** * @hide * Number of Auth failures */ public int numAuthFailures; /** * @hide * Number of reports indicating no Internet Access */ public int numNoInternetAccessReports; /** * @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. */ public boolean hasNoInternetAccess() { return numNoInternetAccessReports > 0 && !validatedInternetAccess; } /** * @hide * Last time we blacklisted the configuration */ public long blackListTimestamp; /** * @hide * Last time the system was connected to this configuration. */ public long lastConnected; /** * @hide * Last time the system tried to connect and failed. */ public long lastConnectionFailure; /** * @hide * Last time the system tried to roam and failed because of authentication failure or DHCP * RENEW failure. */ public long lastRoamingFailure; /** @hide */ public static int ROAMING_FAILURE_IP_CONFIG = 1; /** @hide */ public static int ROAMING_FAILURE_AUTH_FAILURE = 2; /** * @hide * Initial amount of time this Wifi configuration gets blacklisted for network switching * because of roaming failure */ public long roamingFailureBlackListTimeMilli = 1000; /** * @hide * Last roaming failure reason code */ public int lastRoamingFailureReason; /** * @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 */ 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 we didn't auto-join because rssi was too low */ public boolean autoJoinBailedDueToLowRssi; /** * @hide * AutoJoin even though RSSI is 10dB below threshold */ public int autoJoinUseAggressiveJoinAttemptThreshold; /** * @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 * Number of time user disabled WiFi while associated to this configuration with Low RSSI. */ public int numUserTriggeredWifiDisableLowRSSI; /** * @hide * Number of time user disabled WiFi while associated to this configuration with Bad RSSI. */ public int numUserTriggeredWifiDisableBadRSSI; /** * @hide * Number of time user disabled WiFi while associated to this configuration * and RSSI was not HIGH. */ public int numUserTriggeredWifiDisableNotHighRSSI; /** * @hide * Number of ticks associated to this configuration with Low RSSI. */ public int numTicksAtLowRSSI; /** * @hide * Number of ticks associated to this configuration with Bad RSSI. */ public int numTicksAtBadRSSI; /** * @hide * Number of ticks associated to this configuration * and RSSI was not HIGH. */ public int numTicksAtNotHighRSSI; /** * @hide * Number of time user (WifiManager) triggered association to this configuration. * TODO: count this only for Wifi Settings uuid, so as to not count 3rd party apps */ public int numUserTriggeredJoinAttempts; /** * @hide * Connect choices * * remember the keys identifying the known WifiConfiguration over which this configuration * was preferred by user or a "WiFi Network Management app", that is, * a WifiManager.CONNECT_NETWORK or SELECT_NETWORK was received while this configuration * was visible to the user: * configKey is : "SSID"-WEP-WPA_PSK-WPA_EAP * * The integer represents the configuration's RSSI at that time (useful?) * * The overall auto-join algorithm make use of past connect choice so as to sort configuration, * the exact algorithm still fluctuating as of 5/7/2014 * */ public HashMap connectChoices; /** * @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 linkedConfigurations; public WifiConfiguration() { networkId = INVALID_NETWORK_ID; SSID = null; BSSID = null; FQDN = null; naiRealm = null; priority = 0; hiddenSSID = false; disableReason = DISABLED_UNKNOWN_REASON; allowedKeyManagement = new BitSet(); allowedProtocols = new BitSet(); allowedAuthAlgorithms = new BitSet(); allowedPairwiseCiphers = new BitSet(); allowedGroupCiphers = new BitSet(); wepKeys = new String[4]; for (int i = 0; i < wepKeys.length; i++) { wepKeys[i] = null; } enterpriseConfig = new WifiEnterpriseConfig(); autoJoinStatus = AUTO_JOIN_ENABLED; selfAdded = false; didSelfAdd = false; ephemeral = false; validatedInternetAccess = false; mIpConfiguration = new IpConfiguration(); } /** * indicates whether the configuration is valid * @return true if valid, false otherwise * @hide */ public boolean isValid() { if (allowedKeyManagement == null) return false; if (allowedKeyManagement.cardinality() > 1) { if (allowedKeyManagement.cardinality() != 2) { return false; } if (allowedKeyManagement.get(KeyMgmt.WPA_EAP) == false) { return false; } if ((allowedKeyManagement.get(KeyMgmt.IEEE8021X) == false) && (allowedKeyManagement.get(KeyMgmt.WPA_PSK) == false)) { return false; } } // TODO: Add more checks return true; } /** * Helper function, identify if a configuration is linked * @hide */ public boolean isLinked(WifiConfiguration config) { if (config.linkedConfigurations != null && linkedConfigurations != null) { if (config.linkedConfigurations.get(configKey()) != null && linkedConfigurations.get(config.configKey()) != null) { return true; } } return false; } /** * most recent time we have seen this configuration * @return most recent scanResult * @hide */ public ScanResult lastSeen() { ScanResult mostRecent = null; if (scanResultCache == null) { return null; } for (ScanResult result : scanResultCache.values()) { if (mostRecent == null) { if (result.seen != 0) mostRecent = result; } else { if (result.seen > mostRecent.seen) { mostRecent = result; } } } return mostRecent; } /** @hide **/ public void setAutoJoinStatus(int status) { if (status < 0) status = 0; if (status == 0) { blackListTimestamp = 0; } else if (status > autoJoinStatus) { blackListTimestamp = System.currentTimeMillis(); } if (status != autoJoinStatus) { autoJoinStatus = status; dirty = true; } } /** @hide * trim the scan Result Cache * @param: number of entries to keep in the cache */ public void trimScanResultsCache(int num) { if (this.scanResultCache == null) { return; } int currenSize = this.scanResultCache.size(); if (currenSize <= num) { return; // Nothing to trim } ArrayList list = new ArrayList(this.scanResultCache.values()); if (list.size() != 0) { // Sort by descending timestamp Collections.sort(list, new Comparator() { public int compare(Object o1, Object o2) { ScanResult a = (ScanResult)o1; ScanResult b = (ScanResult)o2; if (a.seen > b.seen) { return 1; } if (a.seen < b.seen) { return -1; } return a.BSSID.compareTo(b.BSSID); } }); } for (int i = 0; i < currenSize - num ; i++) { // Remove oldest results from scan cache ScanResult result = list.get(i); this.scanResultCache.remove(result.BSSID); } } /* @hide */ private ArrayList sortScanResults() { ArrayList list = new ArrayList(this.scanResultCache.values()); if (list.size() != 0) { Collections.sort(list, new Comparator() { public int compare(Object o1, Object o2) { ScanResult a = (ScanResult)o1; ScanResult b = (ScanResult)o2; if (a.numIpConfigFailures > b.numIpConfigFailures) { return 1; } if (a.numIpConfigFailures < b.numIpConfigFailures) { return -1; } if (a.seen > b.seen) { return -1; } if (a.seen < b.seen) { return 1; } if (a.level > b.level) { return -1; } if (a.level < b.level) { return 1; } return a.BSSID.compareTo(b.BSSID); } }); } return list; } @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(" BSSID: ").append(this.BSSID).append(" FQDN: ").append(this.FQDN). append(" REALM: ").append(this.naiRealm).append(" PRIO: ").append(this.priority). append('\n'); if (this.numConnectionFailures > 0) { sbuf.append(" numConnectFailures ").append(this.numConnectionFailures).append("\n"); } if (this.numIpConfigFailures > 0) { sbuf.append(" numIpConfigFailures ").append(this.numIpConfigFailures).append("\n"); } if (this.numAuthFailures > 0) { sbuf.append(" numAuthFailures ").append(this.numAuthFailures).append("\n"); } if (this.autoJoinStatus > 0) { sbuf.append(" autoJoinStatus ").append(this.autoJoinStatus).append("\n"); } if (this.disableReason > 0) { sbuf.append(" disableReason ").append(this.disableReason).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.didSelfAdd) sbuf.append(" didSelfAdd"); if (this.selfAdded) sbuf.append(" selfAdded"); if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess"); if (this.ephemeral) sbuf.append(" ephemeral"); if (this.didSelfAdd || this.selfAdded || this.validatedInternetAccess || this.ephemeral) { sbuf.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').append(" PSK: "); if (this.preSharedKey != null) { sbuf.append('*'); } sbuf.append("\nEnterprise config:\n"); sbuf.append(enterpriseConfig); sbuf.append("IP config:\n"); sbuf.append(mIpConfiguration.toString()); if (this.creatorUid != 0) sbuf.append(" uid=" + Integer.toString(creatorUid)); if (this.autoJoinBSSID != null) sbuf.append(" autoJoinBSSID=" + autoJoinBSSID); long now_ms = System.currentTimeMillis(); if (this.blackListTimestamp != 0) { sbuf.append('\n'); long diff = now_ms - this.blackListTimestamp; if (diff <= 0) { sbuf.append(" blackListed since "); } else { sbuf.append(" blackListed: ").append(Long.toString(diff/1000)).append( "sec"); } } if (this.lastConnected != 0) { sbuf.append('\n'); long diff = now_ms - this.lastConnected; if (diff <= 0) { sbuf.append("lastConnected since "); } else { sbuf.append("lastConnected: ").append(Long.toString(diff/1000)).append( "sec"); } } if (this.lastConnectionFailure != 0) { sbuf.append('\n'); long diff = now_ms - this.lastConnectionFailure; if (diff <= 0) { sbuf.append("lastConnectionFailure since "); } else { sbuf.append("lastConnectionFailure: ").append(Long.toString(diff/1000)); sbuf.append( "sec"); } } if (this.lastRoamingFailure != 0) { sbuf.append('\n'); long diff = now_ms - this.lastRoamingFailure; if (diff <= 0) { sbuf.append("lastRoamingFailure since "); } else { sbuf.append("lastRoamingFailure: ").append(Long.toString(diff/1000)); sbuf.append( "sec"); } } sbuf.append("roamingFailureBlackListTimeMilli: "). append(Long.toString(this.roamingFailureBlackListTimeMilli)); sbuf.append('\n'); if (this.linkedConfigurations != null) { for(String key : this.linkedConfigurations.keySet()) { sbuf.append(" linked: ").append(key); sbuf.append('\n'); } } if (this.connectChoices != null) { for(String key : this.connectChoices.keySet()) { Integer choice = this.connectChoices.get(key); if (choice != null) { sbuf.append(" choice: ").append(key); sbuf.append(" = ").append(choice); sbuf.append('\n'); } } } if (this.scanResultCache != null) { sbuf.append("Scan Cache: ").append('\n'); ArrayList list = sortScanResults(); if (list.size() > 0) { for (ScanResult result : list) { long milli = now_ms - result.seen; long ageSec = 0; long ageMin = 0; long ageHour = 0; long ageMilli = 0; long ageDay = 0; if (now_ms > result.seen && result.seen > 0) { ageMilli = milli % 1000; ageSec = (milli / 1000) % 60; ageMin = (milli / (60*1000)) % 60; ageHour = (milli / (60*60*1000)) % 24; ageDay = (milli / (24*60*60*1000)); } sbuf.append("{").append(result.BSSID).append(",").append(result.frequency); sbuf.append(",").append(String.format("%3d", result.level)); if (result.autoJoinStatus > 0) { sbuf.append(",st=").append(result.autoJoinStatus); } if (ageSec > 0 || ageMilli > 0) { sbuf.append(String.format(",%4d.%02d.%02d.%02d.%03dms", ageDay, ageHour, ageMin, ageSec, ageMilli)); } if (result.numIpConfigFailures > 0) { sbuf.append(",ipfail="); sbuf.append(result.numIpConfigFailures); } sbuf.append("} "); } sbuf.append('\n'); } } sbuf.append("triggeredLow: ").append(this.numUserTriggeredWifiDisableLowRSSI); sbuf.append(" triggeredBad: ").append(this.numUserTriggeredWifiDisableBadRSSI); sbuf.append(" triggeredNotHigh: ").append(this.numUserTriggeredWifiDisableNotHighRSSI); sbuf.append('\n'); sbuf.append("ticksLow: ").append(this.numTicksAtLowRSSI); sbuf.append(" ticksBad: ").append(this.numTicksAtBadRSSI); sbuf.append(" ticksNotHigh: ").append(this.numTicksAtNotHighRSSI); sbuf.append('\n'); sbuf.append("triggeredJoin: ").append(this.numUserTriggeredJoinAttempts); sbuf.append('\n'); sbuf.append("autoJoinBailedDueToLowRssi: ").append(this.autoJoinBailedDueToLowRssi); sbuf.append('\n'); sbuf.append("autoJoinUseAggressiveJoinAttemptThreshold: "); sbuf.append(this.autoJoinUseAggressiveJoinAttemptThreshold); sbuf.append('\n'); return sbuf.toString(); } /** * Construct a WifiConfiguration from a scanned network * @param scannedAP the scan result used to construct the config entry * TODO: figure out whether this is a useful way to construct a new entry. * public WifiConfiguration(ScanResult scannedAP) { networkId = -1; SSID = scannedAP.SSID; BSSID = scannedAP.BSSID; } */ /** {@hide} */ 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"" * 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; } /** * 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 = null; 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.IEEE8021X)) { keyMgmt += KeyMgmt.strings[KeyMgmt.IEEE8021X]; } if (TextUtils.isEmpty(keyMgmt)) { throw new IllegalStateException("Not an EAP network"); } return trimStringForKeyId(SSID) + "_" + keyMgmt + "_" + trimStringForKeyId(enterpriseConfig.getKeyId(current != null ? current.enterpriseConfig : null)); } catch (NullPointerException e) { throw new IllegalStateException("Invalid config details"); } } private String trimStringForKeyId(String string) { // 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 */ public int getAuthType() { if (isValid() == false) { throw new IllegalStateException("Invalid configuration"); } 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; } 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 (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 { key = SSID + KeyMgmt.strings[KeyMgmt.NONE]; } mCachedConfigKey = key; } return key; } /** @hide * get configKey, force calculating the config string */ public String configKey() { return configKey(false); } /** @hide * return the config key string based on a scan result */ static public String configKey(ScanResult result) { String key = "\"" + result.SSID + "\""; if (result.capabilities.contains("WEP")) { key = key + "-WEP"; } if (result.capabilities.contains("PSK")) { key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_PSK]; } if (result.capabilities.contains("EAP")) { key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_EAP]; } return key; } /** @hide */ public IpConfiguration getIpConfiguration() { return mIpConfiguration; } /** @hide */ public void setIpConfiguration(IpConfiguration ipConfiguration) { mIpConfiguration = ipConfiguration; } /** @hide */ public StaticIpConfiguration getStaticIpConfiguration() { return mIpConfiguration.getStaticIpConfiguration(); } /** @hide */ public void setStaticIpConfiguration(StaticIpConfiguration staticIpConfiguration) { mIpConfiguration.setStaticIpConfiguration(staticIpConfiguration); } /** @hide */ public IpConfiguration.IpAssignment getIpAssignment() { return mIpConfiguration.ipAssignment; } /** @hide */ public void setIpAssignment(IpConfiguration.IpAssignment ipAssignment) { mIpConfiguration.ipAssignment = ipAssignment; } /** @hide */ public IpConfiguration.ProxySettings getProxySettings() { return mIpConfiguration.proxySettings; } /** @hide */ public void setProxySettings(IpConfiguration.ProxySettings proxySettings) { mIpConfiguration.proxySettings = proxySettings; } /** @hide */ public ProxyInfo getHttpProxy() { return mIpConfiguration.httpProxy; } /** @hide */ public void setHttpProxy(ProxyInfo httpProxy) { mIpConfiguration.httpProxy = httpProxy; } /** @hide */ public void setProxy(ProxySettings settings, ProxyInfo proxy) { mIpConfiguration.proxySettings = settings; mIpConfiguration.httpProxy = proxy; } /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } /** copy constructor {@hide} */ public WifiConfiguration(WifiConfiguration source) { if (source != null) { networkId = source.networkId; status = source.status; disableReason = source.disableReason; disableReason = source.disableReason; SSID = source.SSID; BSSID = source.BSSID; FQDN = source.FQDN; naiRealm = source.naiRealm; preSharedKey = source.preSharedKey; 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(); enterpriseConfig = new WifiEnterpriseConfig(source.enterpriseConfig); defaultGwMacAddress = source.defaultGwMacAddress; mIpConfiguration = new IpConfiguration(source.mIpConfiguration); if ((source.scanResultCache != null) && (source.scanResultCache.size() > 0)) { scanResultCache = new HashMap(); scanResultCache.putAll(source.scanResultCache); } if ((source.connectChoices != null) && (source.connectChoices.size() > 0)) { connectChoices = new HashMap(); connectChoices.putAll(source.connectChoices); } if ((source.linkedConfigurations != null) && (source.linkedConfigurations.size() > 0)) { linkedConfigurations = new HashMap(); linkedConfigurations.putAll(source.linkedConfigurations); } mCachedConfigKey = null; //force null configKey autoJoinStatus = source.autoJoinStatus; selfAdded = source.selfAdded; validatedInternetAccess = source.validatedInternetAccess; ephemeral = source.ephemeral; if (source.visibility != null) { visibility = new Visibility(source.visibility); } lastFailure = source.lastFailure; didSelfAdd = source.didSelfAdd; lastConnectUid = source.lastConnectUid; lastUpdateUid = source.lastUpdateUid; creatorUid = source.creatorUid; peerWifiConfiguration = source.peerWifiConfiguration; blackListTimestamp = source.blackListTimestamp; lastConnected = source.lastConnected; lastDisconnected = source.lastDisconnected; lastConnectionFailure = source.lastConnectionFailure; lastRoamingFailure = source.lastRoamingFailure; lastRoamingFailureReason = source.lastRoamingFailureReason; roamingFailureBlackListTimeMilli = source.roamingFailureBlackListTimeMilli; numConnectionFailures = source.numConnectionFailures; numIpConfigFailures = source.numIpConfigFailures; numAuthFailures = source.numAuthFailures; numScorerOverride = source.numScorerOverride; numScorerOverrideAndSwitchedNetwork = source.numScorerOverrideAndSwitchedNetwork; numAssociation = source.numAssociation; numUserTriggeredWifiDisableLowRSSI = source.numUserTriggeredWifiDisableLowRSSI; numUserTriggeredWifiDisableBadRSSI = source.numUserTriggeredWifiDisableBadRSSI; numUserTriggeredWifiDisableNotHighRSSI = source.numUserTriggeredWifiDisableNotHighRSSI; numTicksAtLowRSSI = source.numTicksAtLowRSSI; numTicksAtBadRSSI = source.numTicksAtBadRSSI; numTicksAtNotHighRSSI = source.numTicksAtNotHighRSSI; numUserTriggeredJoinAttempts = source.numUserTriggeredJoinAttempts; autoJoinBSSID = source.autoJoinBSSID; autoJoinUseAggressiveJoinAttemptThreshold = source.autoJoinUseAggressiveJoinAttemptThreshold; autoJoinBailedDueToLowRssi = source.autoJoinBailedDueToLowRssi; dirty = source.dirty; numNoInternetAccessReports = source.numNoInternetAccessReports; } } /** {@hide} */ //public static final int NOTHING_TAG = 0; /** {@hide} */ //public static final int SCAN_CACHE_TAG = 1; /** Implement the Parcelable interface {@hide} */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(networkId); dest.writeInt(status); dest.writeInt(disableReason); dest.writeString(SSID); dest.writeString(BSSID); dest.writeString(autoJoinBSSID); dest.writeString(FQDN); dest.writeString(naiRealm); 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); dest.writeParcelable(enterpriseConfig, flags); dest.writeParcelable(mIpConfiguration, flags); dest.writeString(dhcpServer); dest.writeString(defaultGwMacAddress); dest.writeInt(autoJoinStatus); dest.writeInt(selfAdded ? 1 : 0); dest.writeInt(didSelfAdd ? 1 : 0); dest.writeInt(validatedInternetAccess ? 1 : 0); dest.writeInt(ephemeral ? 1 : 0); dest.writeInt(creatorUid); dest.writeInt(lastConnectUid); dest.writeInt(lastUpdateUid); dest.writeLong(blackListTimestamp); dest.writeLong(lastConnectionFailure); dest.writeLong(lastRoamingFailure); dest.writeInt(lastRoamingFailureReason); dest.writeLong(roamingFailureBlackListTimeMilli); dest.writeInt(numConnectionFailures); dest.writeInt(numIpConfigFailures); dest.writeInt(numAuthFailures); dest.writeInt(numScorerOverride); dest.writeInt(numScorerOverrideAndSwitchedNetwork); dest.writeInt(numAssociation); dest.writeInt(numUserTriggeredWifiDisableLowRSSI); dest.writeInt(numUserTriggeredWifiDisableBadRSSI); dest.writeInt(numUserTriggeredWifiDisableNotHighRSSI); dest.writeInt(numTicksAtLowRSSI); dest.writeInt(numTicksAtBadRSSI); dest.writeInt(numTicksAtNotHighRSSI); dest.writeInt(numUserTriggeredJoinAttempts); dest.writeInt(autoJoinUseAggressiveJoinAttemptThreshold); dest.writeInt(autoJoinBailedDueToLowRssi ? 1 : 0); dest.writeInt(numNoInternetAccessReports); } /** Implement the Parcelable interface {@hide} */ public static final Creator CREATOR = new Creator() { public WifiConfiguration createFromParcel(Parcel in) { WifiConfiguration config = new WifiConfiguration(); config.networkId = in.readInt(); config.status = in.readInt(); config.disableReason = in.readInt(); config.SSID = in.readString(); config.BSSID = in.readString(); config.autoJoinBSSID = in.readString(); config.FQDN = in.readString(); config.naiRealm = in.readString(); 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.enterpriseConfig = in.readParcelable(null); config.mIpConfiguration = in.readParcelable(null); config.dhcpServer = in.readString(); config.defaultGwMacAddress = in.readString(); config.autoJoinStatus = in.readInt(); config.selfAdded = in.readInt() != 0; config.didSelfAdd = in.readInt() != 0; config.validatedInternetAccess = in.readInt() != 0; config.ephemeral = in.readInt() != 0; config.creatorUid = in.readInt(); config.lastConnectUid = in.readInt(); config.lastUpdateUid = in.readInt(); config.blackListTimestamp = in.readLong(); config.lastConnectionFailure = in.readLong(); config.lastRoamingFailure = in.readLong(); config.lastRoamingFailureReason = in.readInt(); config.roamingFailureBlackListTimeMilli = in.readLong(); config.numConnectionFailures = in.readInt(); config.numIpConfigFailures = in.readInt(); config.numAuthFailures = in.readInt(); config.numScorerOverride = in.readInt(); config.numScorerOverrideAndSwitchedNetwork = in.readInt(); config.numAssociation = in.readInt(); config.numUserTriggeredWifiDisableLowRSSI = in.readInt(); config.numUserTriggeredWifiDisableBadRSSI = in.readInt(); config.numUserTriggeredWifiDisableNotHighRSSI = in.readInt(); config.numTicksAtLowRSSI = in.readInt(); config.numTicksAtBadRSSI = in.readInt(); config.numTicksAtNotHighRSSI = in.readInt(); config.numUserTriggeredJoinAttempts = in.readInt(); config.autoJoinUseAggressiveJoinAttemptThreshold = in.readInt(); config.autoJoinBailedDueToLowRssi = in.readInt() != 0; config.numNoInternetAccessReports = in.readInt(); return config; } public WifiConfiguration[] newArray(int size) { return new WifiConfiguration[size]; } }; }