WifiAutoJoinControllerpublic class WifiAutoJoinController extends Object AutoJoin controller is responsible for WiFi Connect decision
It runs in the thread context of WifiStateMachine |
Fields Summary |
---|
private android.content.Context | mContext | private WifiStateMachine | mWifiStateMachine | private WifiConfigStore | mWifiConfigStore | private WifiNative | mWifiNative | private android.net.NetworkScoreManager | scoreManager | private WifiNetworkScoreCache | mNetworkScoreCache | private static final String | TAG | private static boolean | DBG | private static boolean | VDBG | private static final boolean | mStaStaSupported | public static int | mScanResultMaximumAge | public static int | mScanResultAutoJoinAge | private String | mCurrentConfigurationKey | private HashMap | scanResultCache | private WifiConnectionStatistics | mWifiConnectionStatistics | private boolean | mAllowUntrustedConnectionsWhether to allow connections to untrusted networks. | boolean | didOverride | private static final long | loseBlackListHardMilli | private static final long | loseBlackListSoftMilli | private static final long | DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS | public static final int | AUTO_JOIN_IDLE | public static final int | AUTO_JOIN_ROAMING | public static final int | AUTO_JOIN_EXTENDED_ROAMING | public static final int | AUTO_JOIN_OUT_OF_NETWORK_ROAMING | public static final int | HIGH_THRESHOLD_MODIFIER | boolean | didBailDueToWeakRssiThere was a non-blacklisted configuration that we bailed from because of a weak signal | int | weakRssiBailCountnumber of time we consecutively bailed out of an eligible network because its signal
was too weak |
Constructors Summary |
---|
WifiAutoJoinController(android.content.Context c, WifiStateMachine w, WifiConfigStore s, WifiConnectionStatistics st, WifiNative n)
mContext = c;
mWifiStateMachine = w;
mWifiConfigStore = s;
mWifiNative = n;
mNetworkScoreCache = null;
mWifiConnectionStatistics = st;
scoreManager =
(NetworkScoreManager) mContext.getSystemService(Context.NETWORK_SCORE_SERVICE);
if (scoreManager == null)
logDbg("Registered scoreManager NULL " + " service " + Context.NETWORK_SCORE_SERVICE);
if (scoreManager != null) {
mNetworkScoreCache = new WifiNetworkScoreCache(mContext);
scoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
} else {
logDbg("No network score service: Couldnt register as a WiFi score Manager, type="
+ Integer.toString(NetworkKey.TYPE_WIFI)
+ " service " + Context.NETWORK_SCORE_SERVICE);
mNetworkScoreCache = null;
}
|
Methods Summary |
---|
int | addToScanCache(java.util.List scanList)
int numScanResultsKnown = 0; // Record number of scan results we knew about
WifiConfiguration associatedConfig = null;
boolean didAssociate = false;
long now = System.currentTimeMillis();
ArrayList<NetworkKey> unknownScanResults = new ArrayList<NetworkKey>();
for(ScanResult result: scanList) {
if (result.SSID == null) continue;
// Make sure we record the last time we saw this result
result.seen = System.currentTimeMillis();
// Fetch the previous instance for this result
ScanResult sr = scanResultCache.get(result.BSSID);
if (sr != null) {
if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0
&& result.level == 0
&& sr.level < -20) {
// A 'zero' RSSI reading is most likely a chip problem which returns
// an unknown RSSI, hence ignore it
result.level = sr.level;
}
// If there was a previous cache result for this BSSID, average the RSSI values
result.averageRssi(sr.level, sr.seen, mScanResultMaximumAge);
// Remove the previous Scan Result - this is not necessary
scanResultCache.remove(result.BSSID);
} else if (mWifiConfigStore.scanResultRssiLevelPatchUp != 0 && result.level == 0) {
// A 'zero' RSSI reading is most likely a chip problem which returns
// an unknown RSSI, hence initialize it to a sane value
result.level = mWifiConfigStore.scanResultRssiLevelPatchUp;
}
if (!mNetworkScoreCache.isScoredNetwork(result)) {
WifiKey wkey;
// Quoted SSIDs are the only one valid at this stage
try {
wkey = new WifiKey("\"" + result.SSID + "\"", result.BSSID);
} catch (IllegalArgumentException e) {
logDbg("AutoJoinController: received badly encoded SSID=[" + result.SSID +
"] ->skipping this network");
wkey = null;
}
if (wkey != null) {
NetworkKey nkey = new NetworkKey(wkey);
//if we don't know this scan result then request a score from the scorer
unknownScanResults.add(nkey);
}
if (VDBG) {
String cap = "";
if (result.capabilities != null)
cap = result.capabilities;
logDbg(result.SSID + " " + result.BSSID + " rssi="
+ result.level + " cap " + cap + " is not scored");
}
} else {
if (VDBG) {
String cap = "";
if (result.capabilities != null)
cap = result.capabilities;
int score = mNetworkScoreCache.getNetworkScore(result);
logDbg(result.SSID + " " + result.BSSID + " rssi="
+ result.level + " cap " + cap + " is scored : " + score);
}
}
// scanResultCache.put(result.BSSID, new ScanResult(result));
scanResultCache.put(result.BSSID, result);
// Add this BSSID to the scanResultCache of a Saved WifiConfiguration
didAssociate = mWifiConfigStore.updateSavedNetworkHistory(result);
// If not successful, try to associate this BSSID to an existing Saved WifiConfiguration
if (!didAssociate) {
// We couldn't associate the scan result to a Saved WifiConfiguration
// Hence it is untrusted
result.untrusted = true;
associatedConfig = mWifiConfigStore.associateWithConfiguration(result);
if (associatedConfig != null && associatedConfig.SSID != null) {
if (VDBG) {
logDbg("addToScanCache save associated config "
+ associatedConfig.SSID + " with " + result.SSID
+ " status " + associatedConfig.autoJoinStatus
+ " reason " + associatedConfig.disableReason
+ " tsp " + associatedConfig.blackListTimestamp
+ " was " + (now - associatedConfig.blackListTimestamp));
}
mWifiStateMachine.sendMessage(
WifiStateMachine.CMD_AUTO_SAVE_NETWORK, associatedConfig);
didAssociate = true;
}
} else {
// If the scan result has been blacklisted fir 18 hours -> unblacklist
if ((now - result.blackListTimestamp) > loseBlackListHardMilli) {
result.setAutoJoinStatus(ScanResult.ENABLED);
}
}
if (didAssociate) {
numScanResultsKnown++;
result.isAutoJoinCandidate ++;
} else {
result.isAutoJoinCandidate = 0;
}
}
if (unknownScanResults.size() != 0) {
NetworkKey[] newKeys =
unknownScanResults.toArray(new NetworkKey[unknownScanResults.size()]);
// Kick the score manager, we will get updated scores asynchronously
scoreManager.requestScores(newKeys);
}
return numScanResultsKnown;
| private void | ageScanResultsOut(int delay)Flush out scan results older than mScanResultMaximumAge
if (delay <= 0) {
delay = mScanResultMaximumAge; // Something sane
}
long milli = System.currentTimeMillis();
if (VDBG) {
logDbg("ageScanResultsOut delay " + Integer.valueOf(delay) + " size "
+ Integer.valueOf(scanResultCache.size()) + " now " + Long.valueOf(milli));
}
Iterator<HashMap.Entry<String,ScanResult>> iter = scanResultCache.entrySet().iterator();
while (iter.hasNext()) {
HashMap.Entry<String,ScanResult> entry = iter.next();
ScanResult result = entry.getValue();
if ((result.seen + delay) < milli) {
iter.remove();
}
}
| boolean | attemptAutoJoin()attemptAutoJoin() function implements the core of the a network switching algorithm
Return false if no acceptable networks were found.
boolean found = false;
didOverride = false;
didBailDueToWeakRssi = false;
int networkSwitchType = AUTO_JOIN_IDLE;
long now = System.currentTimeMillis();
String lastSelectedConfiguration = mWifiConfigStore.getLastSelectedConfiguration();
// Reset the currentConfiguration Key, and set it only if WifiStateMachine and
// supplicant agree
mCurrentConfigurationKey = null;
WifiConfiguration currentConfiguration = mWifiStateMachine.getCurrentWifiConfiguration();
WifiConfiguration candidate = null;
// Obtain the subset of recently seen networks
List<WifiConfiguration> list =
mWifiConfigStore.getRecentConfiguredNetworks(mScanResultAutoJoinAge, false);
if (list == null) {
if (VDBG) logDbg("attemptAutoJoin nothing known=" +
mWifiConfigStore.getconfiguredNetworkSize());
return false;
}
// Find the currently connected network: ask the supplicant directly
String val = mWifiNative.status(true);
String status[] = val.split("\\r?\\n");
if (VDBG) {
logDbg("attemptAutoJoin() status=" + val + " split="
+ Integer.toString(status.length));
}
int supplicantNetId = -1;
for (String key : status) {
if (key.regionMatches(0, "id=", 0, 3)) {
int idx = 3;
supplicantNetId = 0;
while (idx < key.length()) {
char c = key.charAt(idx);
if ((c >= 0x30) && (c <= 0x39)) {
supplicantNetId *= 10;
supplicantNetId += c - 0x30;
idx++;
} else {
break;
}
}
} else if (key.contains("wpa_state=ASSOCIATING")
|| key.contains("wpa_state=ASSOCIATED")
|| key.contains("wpa_state=FOUR_WAY_HANDSHAKE")
|| key.contains("wpa_state=GROUP_KEY_HANDSHAKE")) {
if (DBG) {
logDbg("attemptAutoJoin: bail out due to sup state " + key);
}
// After WifiStateMachine ask the supplicant to associate or reconnect
// we might still obtain scan results from supplicant
// however the supplicant state in the mWifiInfo and supplicant state tracker
// are updated when we get the supplicant state change message which can be
// processed after the SCAN_RESULT message, so at this point the framework doesn't
// know that supplicant is ASSOCIATING.
// A good fix for this race condition would be for the WifiStateMachine to add
// a new transient state where it expects to get the supplicant message indicating
// that it started the association process and within which critical operations
// like autojoin should be deleted.
// This transient state would remove the need for the roam Wathchdog which
// basically does that.
// At the moment, we just query the supplicant state synchronously with the
// mWifiNative.status() command, which allow us to know that
// supplicant has started association process, even though we didnt yet get the
// SUPPLICANT_STATE_CHANGE message.
return false;
}
}
if (DBG) {
String conf = "";
String last = "";
if (currentConfiguration != null) {
conf = " current=" + currentConfiguration.configKey();
}
if (lastSelectedConfiguration != null) {
last = " last=" + lastSelectedConfiguration;
}
logDbg("attemptAutoJoin() num recent config " + Integer.toString(list.size())
+ conf + last
+ " ---> suppNetId=" + Integer.toString(supplicantNetId));
}
if (currentConfiguration != null) {
if (supplicantNetId != currentConfiguration.networkId
// https://b.corp.google.com/issue?id=16484607
// mark this condition as an error only if the mismatched networkId are valid
&& supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID
&& currentConfiguration.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
logDbg("attemptAutoJoin() ERROR wpa_supplicant out of sync nid="
+ Integer.toString(supplicantNetId) + " WifiStateMachine="
+ Integer.toString(currentConfiguration.networkId));
mWifiStateMachine.disconnectCommand();
return false;
} else if (currentConfiguration.ephemeral && (!mAllowUntrustedConnections ||
!haveRecentlySeenScoredBssid(currentConfiguration))) {
// The current connection is untrusted (the framework added it), but we're either
// no longer allowed to connect to such networks, the score has been nullified
// since we connected, or the scored BSSID has gone out of range.
// Drop the current connection and perform the rest of autojoin.
logDbg("attemptAutoJoin() disconnecting from unwanted ephemeral network");
mWifiStateMachine.disconnectCommand(Process.WIFI_UID,
mAllowUntrustedConnections ? 1 : 0);
return false;
} else {
mCurrentConfigurationKey = currentConfiguration.configKey();
}
} else {
if (supplicantNetId != WifiConfiguration.INVALID_NETWORK_ID) {
// Maybe in the process of associating, skip this attempt
return false;
}
}
int currentNetId = -1;
if (currentConfiguration != null) {
// If we are associated to a configuration, it will
// be compared thru the compareNetwork function
currentNetId = currentConfiguration.networkId;
}
/**
* Run thru all visible configurations without looking at the one we
* are currently associated to
* select Best Network candidate from known WifiConfigurations
*/
for (WifiConfiguration config : list) {
if (config.SSID == null) {
continue;
}
if (config.autoJoinStatus >=
WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) {
// Wait for 5 minutes before reenabling config that have known,
// repeated connection or DHCP failures
if (config.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE
|| config.disableReason
== WifiConfiguration.DISABLED_ASSOCIATION_REJECT
|| config.disableReason
== WifiConfiguration.DISABLED_AUTH_FAILURE) {
if (config.blackListTimestamp == 0
|| (config.blackListTimestamp > now)) {
// Sanitize the timestamp
config.blackListTimestamp = now;
}
if ((now - config.blackListTimestamp) >
mWifiConfigStore.wifiConfigBlacklistMinTimeMilli) {
// Re-enable the WifiConfiguration
config.status = WifiConfiguration.Status.ENABLED;
// Reset the blacklist condition
config.numConnectionFailures = 0;
config.numIpConfigFailures = 0;
config.numAuthFailures = 0;
config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
config.dirty = true;
} else {
if (VDBG) {
long delay = mWifiConfigStore.wifiConfigBlacklistMinTimeMilli
- (now - config.blackListTimestamp);
logDbg("attemptautoJoin " + config.configKey()
+ " dont unblacklist yet, waiting for "
+ delay + " ms");
}
}
}
// Avoid networks disabled because of AUTH failure altogether
if (DBG) {
logDbg("attemptAutoJoin skip candidate due to auto join status "
+ Integer.toString(config.autoJoinStatus) + " key "
+ config.configKey(true)
+ " reason " + config.disableReason);
}
continue;
}
// Try to un-blacklist based on elapsed time
if (config.blackListTimestamp > 0) {
if (now < config.blackListTimestamp) {
/**
* looks like there was a change in the system clock since we black listed, and
* timestamp is not meaningful anymore, hence lose it.
* this event should be rare enough so that we still want to lose the black list
*/
config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
} else {
if ((now - config.blackListTimestamp) > loseBlackListHardMilli) {
// Reenable it after 18 hours, i.e. next day
config.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
} else if ((now - config.blackListTimestamp) > loseBlackListSoftMilli) {
// Lose blacklisting due to bad link
config.setAutoJoinStatus(config.autoJoinStatus - 8);
}
}
}
// Try to unblacklist based on good visibility
if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Soft
&& config.visibility.rssi24
< mWifiConfigStore.thresholdUnblacklistThreshold24Soft) {
if (DBG) {
logDbg("attemptAutoJoin do not unblacklist due to low visibility "
+ config.autoJoinStatus
+ " key " + config.configKey(true)
+ " rssi=(" + config.visibility.rssi24
+ "," + config.visibility.rssi5
+ ") num=(" + config.visibility.num24
+ "," + config.visibility.num5 + ")");
}
} else if (config.visibility.rssi5 < mWifiConfigStore.thresholdUnblacklistThreshold5Hard
&& config.visibility.rssi24
< mWifiConfigStore.thresholdUnblacklistThreshold24Hard) {
// If the network is simply temporary disabled, don't allow reconnect until
// RSSI becomes good enough
config.setAutoJoinStatus(config.autoJoinStatus - 1);
if (DBG) {
logDbg("attemptAutoJoin good candidate seen, bumped soft -> status="
+ config.autoJoinStatus
+ " " + config.configKey(true) + " rssi=("
+ config.visibility.rssi24 + "," + config.visibility.rssi5
+ ") num=(" + config.visibility.num24
+ "," + config.visibility.num5 + ")");
}
} else {
config.setAutoJoinStatus(config.autoJoinStatus - 3);
if (DBG) {
logDbg("attemptAutoJoin good candidate seen, bumped hard -> status="
+ config.autoJoinStatus
+ " " + config.configKey(true) + " rssi=("
+ config.visibility.rssi24 + "," + config.visibility.rssi5
+ ") num=(" + config.visibility.num24
+ "," + config.visibility.num5 + ")");
}
}
if (config.autoJoinStatus >=
WifiConfiguration.AUTO_JOIN_TEMPORARY_DISABLED) {
// Network is blacklisted, skip
if (DBG) {
logDbg("attemptAutoJoin skip blacklisted -> status="
+ config.autoJoinStatus
+ " " + config.configKey(true) + " rssi=("
+ config.visibility.rssi24 + "," + config.visibility.rssi5
+ ") num=(" + config.visibility.num24
+ "," + config.visibility.num5 + ")");
}
continue;
}
if (config.networkId == currentNetId) {
if (DBG) {
logDbg("attemptAutoJoin skip current candidate "
+ Integer.toString(currentNetId)
+ " key " + config.configKey(true));
}
continue;
}
boolean isLastSelected = false;
if (lastSelectedConfiguration != null &&
config.configKey().equals(lastSelectedConfiguration)) {
isLastSelected = true;
}
if (config.visibility == null) {
continue;
}
if (config.lastRoamingFailure != 0
&& currentConfiguration != null
&& (lastSelectedConfiguration == null
|| !config.configKey().equals(lastSelectedConfiguration))) {
// Apply blacklisting for roaming to this config if:
// - the target config had a recent roaming failure
// - we are currently associated
// - the target config is not the last selected
if (now > config.lastRoamingFailure
&& (now - config.lastRoamingFailure)
< config.roamingFailureBlackListTimeMilli) {
if (DBG) {
logDbg("compareNetwork not switching to " + config.configKey()
+ " from current " + currentConfiguration.configKey()
+ " because it is blacklisted due to roam failure, "
+ " blacklist remain time = "
+ (now - config.lastRoamingFailure) + " ms");
}
continue;
}
}
int boost = config.autoJoinUseAggressiveJoinAttemptThreshold + weakRssiBailCount;
if ((config.visibility.rssi5 + boost)
< mWifiConfigStore.thresholdInitialAutoJoinAttemptMin5RSSI
&& (config.visibility.rssi24 + boost)
< mWifiConfigStore.thresholdInitialAutoJoinAttemptMin24RSSI) {
if (DBG) {
logDbg("attemptAutoJoin skip due to low visibility -> status="
+ config.autoJoinStatus
+ " key " + config.configKey(true) + " rssi="
+ config.visibility.rssi24 + ", " + config.visibility.rssi5
+ " num=" + config.visibility.num24
+ ", " + config.visibility.num5);
}
// Don't try to autojoin a network that is too far but
// If that configuration is a user's choice however, try anyway
if (!isLastSelected) {
config.autoJoinBailedDueToLowRssi = true;
didBailDueToWeakRssi = true;
continue;
} else {
// Next time, try to be a bit more aggressive in auto-joining
if (config.autoJoinUseAggressiveJoinAttemptThreshold
< WifiConfiguration.MAX_INITIAL_AUTO_JOIN_RSSI_BOOST
&& config.autoJoinBailedDueToLowRssi) {
config.autoJoinUseAggressiveJoinAttemptThreshold += 4;
}
}
}
if (config.numNoInternetAccessReports > 0
&& !isLastSelected
&& !config.validatedInternetAccess) {
// Avoid autoJoining this network because last time we used it, it didn't
// have internet access, and we never manage to validate internet access on this
// network configuration
if (DBG) {
logDbg("attemptAutoJoin skip candidate due to no InternetAccess "
+ config.configKey(true)
+ " num reports " + config.numNoInternetAccessReports);
}
continue;
}
if (DBG) {
String cur = "";
if (candidate != null) {
cur = " current candidate " + candidate.configKey();
}
logDbg("attemptAutoJoin trying id="
+ Integer.toString(config.networkId) + " "
+ config.configKey(true)
+ " status=" + config.autoJoinStatus
+ cur);
}
if (candidate == null) {
candidate = config;
} else {
if (VDBG) {
logDbg("attemptAutoJoin will compare candidate " + candidate.configKey()
+ " with " + config.configKey());
}
int order = compareWifiConfigurations(candidate, config);
// The lastSelectedConfiguration is the configuration the user has manually selected
// thru WifiPicker, or that a 3rd party app asked us to connect to via the
// enableNetwork with disableOthers=true WifiManager API
// As this is a direct user choice, we strongly prefer this configuration,
// hence give +/-100
if ((lastSelectedConfiguration != null)
&& candidate.configKey().equals(lastSelectedConfiguration)) {
// candidate is the last selected configuration,
// so keep it above connect choices (+/-60) and
// above RSSI/scorer based selection of linked configuration (+/- 50)
// by reducing order by -100
order = order - 100;
if (VDBG) {
logDbg(" ...and prefers -100 " + candidate.configKey()
+ " over " + config.configKey()
+ " because it is the last selected -> "
+ Integer.toString(order));
}
} else if ((lastSelectedConfiguration != null)
&& config.configKey().equals(lastSelectedConfiguration)) {
// config is the last selected configuration,
// so keep it above connect choices (+/-60) and
// above RSSI/scorer based selection of linked configuration (+/- 50)
// by increasing order by +100
order = order + 100;
if (VDBG) {
logDbg(" ...and prefers +100 " + config.configKey()
+ " over " + candidate.configKey()
+ " because it is the last selected -> "
+ Integer.toString(order));
}
}
if (order > 0) {
// Ascending : candidate < config
candidate = config;
}
}
}
// Now, go thru scan result to try finding a better untrusted network
if (mNetworkScoreCache != null && mAllowUntrustedConnections) {
int rssi5 = WifiConfiguration.INVALID_RSSI;
int rssi24 = WifiConfiguration.INVALID_RSSI;
if (candidate != null) {
rssi5 = candidate.visibility.rssi5;
rssi24 = candidate.visibility.rssi24;
}
// Get current date
long nowMs = System.currentTimeMillis();
int currentScore = -10000;
// The untrusted network with highest score
ScanResult untrustedCandidate = null;
// Look for untrusted scored network only if the current candidate is bad
if (isBadCandidate(rssi24, rssi5)) {
for (ScanResult result : scanResultCache.values()) {
// We look only at untrusted networks with a valid SSID
// A trusted result would have been looked at thru it's Wificonfiguration
if (TextUtils.isEmpty(result.SSID) || !result.untrusted ||
!isOpenNetwork(result)) {
continue;
}
String quotedSSID = "\"" + result.SSID + "\"";
if (mWifiConfigStore.mDeletedEphemeralSSIDs.contains(quotedSSID)) {
// SSID had been Forgotten by user, then don't score it
continue;
}
if ((nowMs - result.seen) < mScanResultAutoJoinAge) {
// Increment usage count for the network
mWifiConnectionStatistics.incrementOrAddUntrusted(quotedSSID, 0, 1);
boolean isActiveNetwork = currentConfiguration != null
&& currentConfiguration.SSID.equals(quotedSSID);
int score = mNetworkScoreCache.getNetworkScore(result, isActiveNetwork);
if (score != WifiNetworkScoreCache.INVALID_NETWORK_SCORE
&& score > currentScore) {
// Highest score: Select this candidate
currentScore = score;
untrustedCandidate = result;
if (VDBG) {
logDbg("AutoJoinController: found untrusted candidate "
+ result.SSID
+ " RSSI=" + result.level
+ " freq=" + result.frequency
+ " score=" + score);
}
}
}
}
}
if (untrustedCandidate != null) {
// At this point, we have an untrusted network candidate.
// Create the new ephemeral configuration and see if we should switch over
candidate =
mWifiConfigStore.wifiConfigurationFromScanResult(untrustedCandidate);
candidate.allowedKeyManagement.set(KeyMgmt.NONE);
candidate.ephemeral = true;
}
}
long lastUnwanted =
System.currentTimeMillis()
- mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp;
if (candidate == null
&& lastSelectedConfiguration == null
&& currentConfiguration == null
&& didBailDueToWeakRssi
&& (mWifiConfigStore.lastUnwantedNetworkDisconnectTimestamp == 0
|| lastUnwanted > (1000 * 60 * 60 * 24 * 7))
) {
// We are bailing out of autojoin although we are seeing a weak configuration, and
// - we didn't find another valid candidate
// - we are not connected
// - without a user network selection choice
// - ConnectivityService has not triggered an unwanted network disconnect
// on this device for a week (hence most likely there is no SIM card or cellular)
// If all those conditions are met, then boost the RSSI of the weak networks
// that we are seeing so as we will eventually pick one
if (weakRssiBailCount < 10)
weakRssiBailCount += 1;
} else {
if (weakRssiBailCount > 0)
weakRssiBailCount -= 1;
}
/**
* If candidate is found, check the state of the connection so as
* to decide if we should be acting on this candidate and switching over
*/
int networkDelta = compareNetwork(candidate, lastSelectedConfiguration);
if (DBG && candidate != null) {
String doSwitch = "";
String current = "";
if (networkDelta < 0) {
doSwitch = " -> not switching";
}
if (currentConfiguration != null) {
current = " with current " + currentConfiguration.configKey();
}
logDbg("attemptAutoJoin networkSwitching candidate "
+ candidate.configKey()
+ current
+ " linked=" + (currentConfiguration != null
&& currentConfiguration.isLinked(candidate))
+ " : delta="
+ Integer.toString(networkDelta) + " "
+ doSwitch);
}
/**
* Ask WifiStateMachine permission to switch :
* if user is currently streaming voice traffic,
* then we should not be allowed to switch regardless of the delta
*/
if (mWifiStateMachine.shouldSwitchNetwork(networkDelta)) {
if (mStaStaSupported) {
logDbg("mStaStaSupported --> error do nothing now ");
} else {
if (currentConfiguration != null && currentConfiguration.isLinked(candidate)) {
networkSwitchType = AUTO_JOIN_EXTENDED_ROAMING;
} else {
networkSwitchType = AUTO_JOIN_OUT_OF_NETWORK_ROAMING;
}
if (DBG) {
logDbg("AutoJoin auto connect with netId "
+ Integer.toString(candidate.networkId)
+ " to " + candidate.configKey());
}
if (didOverride) {
candidate.numScorerOverrideAndSwitchedNetwork++;
}
candidate.numAssociation++;
mWifiConnectionStatistics.numAutoJoinAttempt++;
if (candidate.ephemeral) {
// We found a new candidate that we are going to connect to, then
// increase its connection count
mWifiConnectionStatistics.
incrementOrAddUntrusted(candidate.SSID, 1, 0);
}
if (candidate.BSSID == null || candidate.BSSID.equals("any")) {
// First step we selected the configuration we want to connect to
// Second step: Look for the best Scan result for this configuration
// TODO this algorithm should really be done in one step
String currentBSSID = mWifiStateMachine.getCurrentBSSID();
ScanResult roamCandidate =
attemptRoam(null, candidate, mScanResultAutoJoinAge, null);
if (roamCandidate != null && currentBSSID != null
&& currentBSSID.equals(roamCandidate.BSSID)) {
// Sanity, we were already asociated to that candidate
roamCandidate = null;
}
if (roamCandidate != null && roamCandidate.is5GHz()) {
// If the configuration hasn't a default BSSID selected, and the best
// candidate is 5GHZ, then select this candidate so as WifiStateMachine and
// supplicant will pick it first
candidate.autoJoinBSSID = roamCandidate.BSSID;
if (VDBG) {
logDbg("AutoJoinController: lock to 5GHz "
+ candidate.autoJoinBSSID
+ " RSSI=" + roamCandidate.level
+ " freq=" + roamCandidate.frequency);
}
} else {
// We couldnt find a roam candidate
candidate.autoJoinBSSID = "any";
}
}
mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_CONNECT,
candidate.networkId, networkSwitchType, candidate);
found = true;
}
}
if (networkSwitchType == AUTO_JOIN_IDLE) {
String currentBSSID = mWifiStateMachine.getCurrentBSSID();
// Attempt same WifiConfiguration roaming
ScanResult roamCandidate =
attemptRoam(null, currentConfiguration, mScanResultAutoJoinAge, currentBSSID);
/**
* TODO: (post L initial release)
* consider handling linked configurations roaming (i.e. extended Roaming)
* thru the attemptRoam function which makes use of the RSSI roaming threshold.
* At the moment, extended roaming is only handled thru the attemptAutoJoin()
* function which compare configurations.
*
* The advantage of making use of attemptRoam function is that this function
* will looks at all the BSSID of each configurations, instead of only looking
* at WifiConfiguration.visibility which keeps trackonly of the RSSI/band of the
* two highest BSSIDs.
*/
// Attempt linked WifiConfiguration roaming
/* if (currentConfiguration != null
&& currentConfiguration.linkedConfigurations != null) {
for (String key : currentConfiguration.linkedConfigurations.keySet()) {
WifiConfiguration link = mWifiConfigStore.getWifiConfiguration(key);
if (link != null) {
roamCandidate = attemptRoam(roamCandidate, link, mScanResultAutoJoinAge,
currentBSSID);
}
}
}*/
if (roamCandidate != null && currentBSSID != null
&& currentBSSID.equals(roamCandidate.BSSID)) {
roamCandidate = null;
}
if (roamCandidate != null && mWifiStateMachine.shouldSwitchNetwork(999)) {
if (DBG) {
logDbg("AutoJoin auto roam with netId "
+ Integer.toString(currentConfiguration.networkId)
+ " " + currentConfiguration.configKey() + " to BSSID="
+ roamCandidate.BSSID + " freq=" + roamCandidate.frequency
+ " RSSI=" + roamCandidate.level);
}
networkSwitchType = AUTO_JOIN_ROAMING;
mWifiConnectionStatistics.numAutoRoamAttempt++;
mWifiStateMachine.sendMessage(WifiStateMachine.CMD_AUTO_ROAM,
currentConfiguration.networkId, 1, roamCandidate);
found = true;
}
}
if (VDBG) logDbg("Done attemptAutoJoin status=" + Integer.toString(networkSwitchType));
return found;
| public ScanResult | attemptRoam(ScanResult a, WifiConfiguration current, int age, java.lang.String currentBSSID)attemptRoam() function implements the core of the same SSID switching algorithm
Run thru all recent scan result of a WifiConfiguration and select the
best one.
if (current == null) {
if (VDBG) {
logDbg("attemptRoam not associated");
}
return a;
}
if (current.scanResultCache == null) {
if (VDBG) {
logDbg("attemptRoam no scan cache");
}
return a;
}
if (current.scanResultCache.size() > 6) {
if (VDBG) {
logDbg("attemptRoam scan cache size "
+ current.scanResultCache.size() + " --> bail");
}
// Implement same SSID roaming only for configurations
// that have less than 4 BSSIDs
return a;
}
if (current.BSSID != null && !current.BSSID.equals("any")) {
if (DBG) {
logDbg("attemptRoam() BSSID is set "
+ current.BSSID + " -> bail");
}
return a;
}
// Determine which BSSID we want to associate to, taking account
// relative strength of 5 and 2.4 GHz BSSIDs
long nowMs = System.currentTimeMillis();
for (ScanResult b : current.scanResultCache.values()) {
int bRssiBoost5 = 0;
int aRssiBoost5 = 0;
int bRssiBoost = 0;
int aRssiBoost = 0;
if ((b.seen == 0) || (b.BSSID == null)
|| ((nowMs - b.seen) > age)
|| b.autoJoinStatus != ScanResult.ENABLED
|| b.numIpConfigFailures > 8) {
continue;
}
// Pick first one
if (a == null) {
a = b;
continue;
}
if (b.numIpConfigFailures < (a.numIpConfigFailures - 1)) {
// Prefer a BSSID that doesn't have less number of Ip config failures
logDbg("attemptRoam: "
+ b.BSSID + " rssi=" + b.level + " ipfail=" +b.numIpConfigFailures
+ " freq=" + b.frequency
+ " > "
+ a.BSSID + " rssi=" + a.level + " ipfail=" +a.numIpConfigFailures
+ " freq=" + a.frequency);
a = b;
continue;
}
// Apply hysteresis: we favor the currentBSSID by giving it a boost
if (currentBSSID != null && currentBSSID.equals(b.BSSID)) {
// Reduce the benefit of hysteresis if RSSI <= -75
if (b.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) {
bRssiBoost = mWifiConfigStore.associatedHysteresisLow;
} else {
bRssiBoost = mWifiConfigStore.associatedHysteresisHigh;
}
}
if (currentBSSID != null && currentBSSID.equals(a.BSSID)) {
if (a.level <= mWifiConfigStore.bandPreferencePenaltyThreshold5) {
// Reduce the benefit of hysteresis if RSSI <= -75
aRssiBoost = mWifiConfigStore.associatedHysteresisLow;
} else {
aRssiBoost = mWifiConfigStore.associatedHysteresisHigh;
}
}
// Favor 5GHz: give a boost to 5GHz BSSIDs, with a slightly progressive curve
// Boost the BSSID if it is on 5GHz, above a threshold
// But penalize it if it is on 5GHz and below threshold
//
// With he current threshold values, 5GHz network with RSSI above -55
// Are given a boost of 30DB which is enough to overcome the current BSSID
// hysteresis (+14) plus 2.4/5 GHz signal strength difference on most cases
//
// The "current BSSID" Boost must be added to the BSSID's level so as to introduce\
// soem amount of hysteresis
if (b.is5GHz()) {
bRssiBoost5 = rssiBoostFrom5GHzRssi(b.level + bRssiBoost, b.BSSID);
}
if (a.is5GHz()) {
aRssiBoost5 = rssiBoostFrom5GHzRssi(a.level + aRssiBoost, a.BSSID);
}
if (VDBG) {
String comp = " < ";
if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
comp = " > ";
}
logDbg("attemptRoam: "
+ b.BSSID + " rssi=" + b.level + " boost=" + Integer.toString(bRssiBoost)
+ "/" + Integer.toString(bRssiBoost5) + " freq=" + b.frequency
+ comp
+ a.BSSID + " rssi=" + a.level + " boost=" + Integer.toString(aRssiBoost)
+ "/" + Integer.toString(aRssiBoost5) + " freq=" + a.frequency);
}
// Compare the RSSIs after applying the hysteresis boost and the 5GHz
// boost if applicable
if (b.level + bRssiBoost + bRssiBoost5 > a.level +aRssiBoost + aRssiBoost5) {
// b is the better BSSID
a = b;
}
}
if (a != null) {
if (VDBG) {
StringBuilder sb = new StringBuilder();
sb.append("attemptRoam: " + current.configKey() +
" Found " + a.BSSID + " rssi=" + a.level + " freq=" + a.frequency);
if (currentBSSID != null) {
sb.append(" Current: " + currentBSSID);
}
sb.append("\n");
logDbg(sb.toString());
}
}
return a;
| private int | compareNetwork(WifiConfiguration candidate, java.lang.String lastSelectedConfiguration)compare a WifiConfiguration against the current network, return a delta score
If not associated, and the candidate will always be better
For instance if the candidate is a home network versus an unknown public wifi,
the delta will be infinite, else compare Kepler scores etcâ |
|