/*
* Copyright (C) 2014 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 com.android.server.wifi;
import android.content.Context;
import android.net.NetworkKey;
import android.net.NetworkScoreManager;
import android.net.WifiKey;
import android.net.wifi.*;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.os.SystemClock;
import android.provider.Settings;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
/**
* AutoJoin controller is responsible for WiFi Connect decision
*
* It runs in the thread context of WifiStateMachine
*
*/
public class WifiAutoJoinController {
private Context mContext;
private WifiStateMachine mWifiStateMachine;
private WifiConfigStore mWifiConfigStore;
private WifiNative mWifiNative;
private NetworkScoreManager scoreManager;
private WifiNetworkScoreCache mNetworkScoreCache;
private static final String TAG = "WifiAutoJoinController ";
private static boolean DBG = false;
private static boolean VDBG = false;
private static final boolean mStaStaSupported = false;
public static int mScanResultMaximumAge = 40000; /* milliseconds unit */
public static int mScanResultAutoJoinAge = 5000; /* milliseconds unit */
private String mCurrentConfigurationKey = null; //used by autojoin
private HashMap<String, ScanResult> scanResultCache =
new HashMap<String, ScanResult>();
private WifiConnectionStatistics mWifiConnectionStatistics;
/** Whether to allow connections to untrusted networks. */
private boolean mAllowUntrustedConnections = false;
/* For debug purpose only: if the scored override a score */
boolean didOverride = false;
// Lose the non-auth failure blacklisting after 8 hours
private final static long loseBlackListHardMilli = 1000 * 60 * 60 * 8;
// Lose some temporary blacklisting after 30 minutes
private final static long loseBlackListSoftMilli = 1000 * 60 * 30;
/** @see android.provider.Settings.Global#WIFI_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS */
private static final long DEFAULT_EPHEMERAL_OUT_OF_RANGE_TIMEOUT_MS = 1000 * 60; // 1 minute
public static final int AUTO_JOIN_IDLE = 0;
public static final int AUTO_JOIN_ROAMING = 1;
public static final int AUTO_JOIN_EXTENDED_ROAMING = 2;
public static final int AUTO_JOIN_OUT_OF_NETWORK_ROAMING = 3;
public static final int HIGH_THRESHOLD_MODIFIER = 5;
// Below are AutoJoin wide parameters indicating if we should be aggressive before joining
// weak network. Note that we cannot join weak network that are going to be marked as unanted by
// ConnectivityService because this will trigger link flapping.
/**
* There was a non-blacklisted configuration that we bailed from because of a weak signal
*/
boolean didBailDueToWeakRssi = false;
/**
* number of time we consecutively bailed out of an eligible network because its signal
* was too weak
*/
int weakRssiBailCount = 0;
WifiAutoJoinController(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;
}
}
void enableVerboseLogging(int verbose) {
if (verbose > 0 ) {
DBG = true;
VDBG = true;
} else {
DBG = false;
VDBG = false;
}
}
/**
* Flush out scan results older than mScanResultMaximumAge
*
*/
private void ageScanResultsOut(int delay) {
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();
}
}
}
int addToScanCache(List<ScanResult> 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;
}
void logDbg(String message) {
logDbg(message, false);
}
void logDbg(String message, boolean stackTrace) {
if (stackTrace) {
Log.e(TAG, message + " stack:"
+ Thread.currentThread().getStackTrace()[2].getMethodName() + " - "
+ Thread.currentThread().getStackTrace()[3].getMethodName() + " - "
+ Thread.currentThread().getStackTrace()[4].getMethodName() + " - "
+ Thread.currentThread().getStackTrace()[5].getMethodName());
} else {
Log.e(TAG, message);
}
}
// Called directly from WifiStateMachine
int newSupplicantResults(boolean doAutoJoin) {
int numScanResultsKnown;
List<ScanResult> scanList = mWifiStateMachine.getScanResultsListNoCopyUnsync();
numScanResultsKnown = addToScanCache(scanList);
ageScanResultsOut(mScanResultMaximumAge);
if (DBG) {
logDbg("newSupplicantResults size=" + Integer.valueOf(scanResultCache.size())
+ " known=" + numScanResultsKnown + " "
+ doAutoJoin);
}
if (doAutoJoin) {
attemptAutoJoin();
}
mWifiConfigStore.writeKnownNetworkHistory(false);
return numScanResultsKnown;
}
/**
* Not used at the moment
* should be a call back from WifiScanner HAL ??
* this function is not hooked and working yet, it will receive scan results from WifiScanners
* with the list of IEs,then populate the capabilities by parsing the IEs and inject the scan
* results as normal.
*/
void newHalScanResults() {
List<ScanResult> scanList = null;//mWifiScanner.syncGetScanResultsList();
String akm = WifiParser.parse_akm(null, null);
logDbg(akm);
addToScanCache(scanList);
ageScanResultsOut(0);
attemptAutoJoin();
mWifiConfigStore.writeKnownNetworkHistory(false);
}
/**
* network link quality changed, called directly from WifiTrafficPoller,
* or by listening to Link Quality intent
*/
void linkQualitySignificantChange() {
attemptAutoJoin();
}
/**
* 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â |