diff options
author | Randy Pan <zpan@google.com> | 2016-09-02 15:15:53 -0700 |
---|---|---|
committer | Randy Pan <zpan@google.com> | 2016-09-27 10:58:45 -0700 |
commit | f2a46f11dd92f2820e96b1b8b69b433012d6bcef (patch) | |
tree | fd1050f55dd44410678d69cb94b606585577061b /service | |
parent | cf01af187323784ef5f033148928a0344ffe1ece (diff) |
Wifi Network Selector
Set up the layout of the new Wifi Network Selector. Add the implementation
of Save Network Evaluator and Exernally Scored Network Evaluator.
Bug: 31089538
Test: unit tests and manual tests
Merged-In: Ieb269060669df220c3d1777eb3aac3b119d4fdc1
Change-Id: Ieb269060669df220c3d1777eb3aac3b119d4fdc1
Diffstat (limited to 'service')
6 files changed, 1392 insertions, 1286 deletions
diff --git a/service/java/com/android/server/wifi/ExternalScoreEvaluator.java b/service/java/com/android/server/wifi/ExternalScoreEvaluator.java new file mode 100644 index 000000000..03d89b040 --- /dev/null +++ b/service/java/com/android/server/wifi/ExternalScoreEvaluator.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2016 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.annotation.Nullable; +import android.content.Context; +import android.net.NetworkKey; +import android.net.NetworkScoreManager; +import android.net.WifiKey; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.os.Process; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; + +import com.android.server.wifi.util.ScanResultUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class is the WifiNetworkSelector.NetworkEvaluator implementation for + * externally scored networks. + */ +public class ExternalScoreEvaluator implements WifiNetworkSelector.NetworkEvaluator { + private static final String NAME = "WifiExternalScoreEvaluator"; + private static final String TAG = NAME; + private final WifiConfigManager mWifiConfigManager; + private final Clock mClock; + private final LocalLog mLocalLog; + private final NetworkScoreManager mScoreManager; + private final WifiNetworkScoreCache mScoreCache; + + ExternalScoreEvaluator(Context context, WifiConfigManager configManager, Clock clock, + LocalLog localLog) { + mWifiConfigManager = configManager; + mClock = clock; + mLocalLog = localLog; + mScoreManager = + (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE); + if (mScoreManager != null) { + mScoreCache = new WifiNetworkScoreCache(context); + mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache); + } else { + Log.e(TAG, "Couldn't get NETWORK_SCORE_SERVICE."); + mScoreCache = null; + } + } + + private void localLog(String log) { + if (mLocalLog != null) { + mLocalLog.log(log); + } + } + + /** + * Get the evaluator name. + */ + public String getName() { + return NAME; + } + + private void updateNetworkScoreCache(List<ScanDetail> scanDetails) { + ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>(); + + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + + // Is there a score for this network? If not, request a score. + if (mScoreCache != null && !mScoreCache.isScoredNetwork(scanResult)) { + WifiKey wifiKey; + + try { + wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID); + NetworkKey ntwkKey = new NetworkKey(wifiKey); + unscoredNetworks.add(ntwkKey); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID + + " for network score. Skip."); + } + } + } + + // Kick the score manager if there is any unscored network. + if (mScoreManager != null && unscoredNetworks.size() != 0) { + NetworkKey[] unscoredNetworkKeys = + unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]); + mScoreManager.requestScores(unscoredNetworkKeys); + } + } + + /** + * Update the evaluator. + */ + public void update(List<ScanDetail> scanDetails) { + updateNetworkScoreCache(scanDetails); + } + + private boolean isPotentialEphemeralNetwork(List<WifiConfiguration> associatedConfigurations) { + if (associatedConfigurations == null) { + return true; + } else if (associatedConfigurations.size() == 1) { + // If there is more than one associated networks, it must be a passpoint network. + // Hence it is not a ephemeral network. + WifiConfiguration network = associatedConfigurations.get(0); + if (network.ephemeral) { + return true; + } + } + return false; + } + + private WifiConfiguration getPotentialEphemeralNetworkConfiguration( + List<WifiConfiguration> associatedConfigurations) { + if (associatedConfigurations == null) { + return null; + } else { + WifiConfiguration network = associatedConfigurations.get(0); + return network; + } + } + + /** + * Returns the available external network score or null if no score is available. + * + * @param scanResult The scan result of the network to score. + * @return A valid external score if one is available or NULL. + */ + @Nullable + Integer getNetworkScore(ScanResult scanResult, WifiNetworkScoreCache scoreCache) { + if (scoreCache != null && scoreCache.isScoredNetwork(scanResult)) { + int score = scoreCache.getNetworkScore(scanResult, false); + localLog(WifiNetworkSelector.toScanId(scanResult) + " has score: " + score); + return score; + } + return null; + } + + /** + * Returns the best candidate network according to the given ExternalScoreEvaluator. + */ + @Nullable + WifiConfiguration getExternalScoreCandidate(ExternalScoreTracker scoreTracker, + WifiNetworkScoreCache scoreCache) { + int candidateNetworkId = WifiConfiguration.INVALID_NETWORK_ID; + switch (scoreTracker.getBestCandidateType()) { + case ExternalScoreTracker.EXTERNAL_SCORED_UNTRUSTED_NETWORK: + ScanResult untrustedScanResultCandidate = + scoreTracker.getScanResultCandidate(); + WifiConfiguration unTrustedNetworkCandidate = + ScanResultUtil.createNetworkFromScanResult(untrustedScanResultCandidate); + + // Mark this config as ephemeral so it isn't persisted. + unTrustedNetworkCandidate.ephemeral = true; + if (scoreCache != null) { + unTrustedNetworkCandidate.meteredHint = + scoreCache.getMeteredHint(untrustedScanResultCandidate); + } + NetworkUpdateResult result = + mWifiConfigManager.addOrUpdateNetwork(unTrustedNetworkCandidate, + Process.WIFI_UID); + if (!result.isSuccess()) { + Log.e(TAG, "Failed to add ephemeral network"); + break; + } + candidateNetworkId = result.getNetworkId(); + mWifiConfigManager.setNetworkCandidateScanResult(candidateNetworkId, + untrustedScanResultCandidate, 0); + localLog(String.format("new ephemeral candidate %s network ID:%d, " + + "meteredHint=%b", + WifiNetworkSelector.toScanId(untrustedScanResultCandidate), + candidateNetworkId, + unTrustedNetworkCandidate.meteredHint)); + break; + + case ExternalScoreTracker.EXTERNAL_SCORED_SAVED_NETWORK: + ScanResult scanResultCandidate = scoreTracker.getScanResultCandidate(); + candidateNetworkId = scoreTracker.getSavedConfig().networkId; + mWifiConfigManager.setNetworkCandidateScanResult(candidateNetworkId, + scanResultCandidate, 0); + localLog(String.format("new saved network candidate %s network ID:%d", + WifiNetworkSelector.toScanId(scanResultCandidate), + candidateNetworkId)); + break; + + case ExternalScoreTracker.EXTERNAL_SCORED_NONE: + localLog("did not see any good candidates."); + break; + + default: + localLog("Unhandled case. No candidate selected."); + break; + } + return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId); + } + + /** + * Evaluate all the networks from the scan results and return + * the WifiConfiguration of the network chosen for connection. + * + * @return configuration of the chosen network; + * null if no network in this category is available. + */ + public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, + WifiConfiguration currentNetwork, String currentBssid, boolean connected, + boolean untrustedNetworkAllowed, + List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) { + if (mScoreCache == null) { + localLog("has no network score cache."); + return null; + } + + final ExternalScoreTracker externalScoreTracker = new ExternalScoreTracker(mLocalLog); + ArrayList<NetworkKey> unscoredNetworks = new ArrayList<>(); + + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + + // One ScanResult can be associated with more than one networks, hence we calculate all + // the scores and use the highest one as the ScanResult's score. + // TODO(b/31065385): WifiConfigManager does not support passpoint networks currently. + // So this list has just one entry always. + List<WifiConfiguration> associatedConfigs = null; + WifiConfiguration associatedConfig = + mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); + if (associatedConfig != null) { + associatedConfigs = + new ArrayList<>(Arrays.asList(associatedConfig)); + } + + if (isPotentialEphemeralNetwork(associatedConfigs)) { + if (untrustedNetworkAllowed) { + if (!mWifiConfigManager.wasEphemeralNetworkDeleted(scanResult.SSID)) { + Integer score = getNetworkScore(scanResult, mScoreCache); + externalScoreTracker.trackUntrustedCandidate(score, scanResult); + if (connectableNetworks != null) { + connectableNetworks.add(Pair.create(scanDetail, + getPotentialEphemeralNetworkConfiguration(associatedConfigs))); + } + } + } + continue; + } + + for (WifiConfiguration network : associatedConfigs) { + WifiConfiguration.NetworkSelectionStatus status = + network.getNetworkSelectionStatus(); + status.setSeenInLastQualifiedNetworkSelection(true); + if (!status.isNetworkEnabled()) { + continue; + } else if (network.BSSID != null && !network.BSSID.equals("any") + && !network.BSSID.equals(scanResult.BSSID)) { + // App has specified the only BSSID to connect for this + // configuration. So only the matching ScanResult can be a candidate. + localLog("Network " + WifiNetworkSelector.toNetworkString(network) + + " has specified BSSID " + network.BSSID + ". Skip " + + scanResult.BSSID); + continue; + } + + // Saved network wth an external score. + if (network.useExternalScores) { + localLog("Network " + WifiNetworkSelector.toNetworkString(network) + + " uses external score"); + Integer score = getNetworkScore(scanResult, mScoreCache); + externalScoreTracker.trackSavedCandidate(score, network, scanResult); + if (connectableNetworks != null) { + connectableNetworks.add(Pair.create(scanDetail, network)); + } + } + } + } + + WifiConfiguration candidate = getExternalScoreCandidate(externalScoreTracker, mScoreCache); + + if (candidate != null + && candidate.getNetworkSelectionStatus().getCandidate() != null) { + return candidate; + } else { + return null; + } + } + + /** + * Used to track the network with the highest score. + */ + static class ExternalScoreTracker { + public static final int EXTERNAL_SCORED_NONE = 0; + public static final int EXTERNAL_SCORED_SAVED_NETWORK = 1; + public static final int EXTERNAL_SCORED_UNTRUSTED_NETWORK = 2; + + private int mBestCandidateType = EXTERNAL_SCORED_NONE; + private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; + private WifiConfiguration mSavedConfig; + private ScanResult mScanResultCandidate; + private final LocalLog mLocalLog; + + ExternalScoreTracker(LocalLog localLog) { + mLocalLog = localLog; + } + + // Determines whether or not the given scan result is the best one its seen so far. + void trackUntrustedCandidate(@Nullable Integer score, ScanResult scanResult) { + if (score != null && score > mHighScore) { + mHighScore = score; + mScanResultCandidate = scanResult; + mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK; + localLog(WifiNetworkSelector.toScanId(scanResult) + + " becomes the new untrusted candidate."); + } + } + + // Determines whether or not the given saved network is the best one its seen so far. + void trackSavedCandidate(@Nullable Integer score, WifiConfiguration config, + ScanResult scanResult) { + // Always take the highest score. If there's a tie and an untrusted network is currently + // the best then pick the saved network. + if (score != null + && (score > mHighScore + || (mBestCandidateType == EXTERNAL_SCORED_UNTRUSTED_NETWORK + && score == mHighScore))) { + mHighScore = score; + mSavedConfig = config; + mScanResultCandidate = scanResult; + mBestCandidateType = EXTERNAL_SCORED_SAVED_NETWORK; + localLog(WifiNetworkSelector.toScanId(scanResult) + + " becomes the new externally scored saved network candidate."); + } + } + + int getBestCandidateType() { + return mBestCandidateType; + } + + int getHighScore() { + return mHighScore; + } + + public ScanResult getScanResultCandidate() { + return mScanResultCandidate; + } + + WifiConfiguration getSavedConfig() { + return mSavedConfig; + } + + private void localLog(String log) { + if (mLocalLog != null) { + mLocalLog.log(log); + } + } + } +} diff --git a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java new file mode 100644 index 000000000..afd9b583c --- /dev/null +++ b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2016 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.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.util.LocalLog; +import android.util.Pair; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This class is the WifiNetworkSelector.NetworkEvaluator implementation for + * saved networks. + */ +public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator { + private static final String NAME = "WifiSavedNetworkEvaluator"; + private final WifiConfigManager mWifiConfigManager; + private final Clock mClock; + private final LocalLog mLocalLog; + private final int mRssiScoreSlope; + private final int mRssiScoreOffset; + private final int mSameBssidAward; + private final int mSameNetworkAward; + private final int mBand5GHzAward; + private final int mLastSelectionAward; + private final int mPasspointSecurityAward; + private final int mSecurityAward; + private final int mNoInternetPenalty; + private final int mThresholdSaturatedRssi24; + + SavedNetworkEvaluator(Context context, WifiConfigManager configManager, + Clock clock, LocalLog localLog) { + mWifiConfigManager = configManager; + mClock = clock; + mLocalLog = localLog; + + mRssiScoreSlope = context.getResources().getInteger( + R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); + mRssiScoreOffset = context.getResources().getInteger( + R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); + mSameBssidAward = context.getResources().getInteger( + R.integer.config_wifi_framework_SAME_BSSID_AWARD); + mSameNetworkAward = context.getResources().getInteger( + R.integer.config_wifi_framework_current_network_boost); + mLastSelectionAward = context.getResources().getInteger( + R.integer.config_wifi_framework_LAST_SELECTION_AWARD); + mPasspointSecurityAward = context.getResources().getInteger( + R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD); + mSecurityAward = context.getResources().getInteger( + R.integer.config_wifi_framework_SECURITY_AWARD); + mBand5GHzAward = context.getResources().getInteger( + R.integer.config_wifi_framework_5GHz_preference_boost_factor); + mThresholdSaturatedRssi24 = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); + mNoInternetPenalty = (mThresholdSaturatedRssi24 + mRssiScoreOffset) + * mRssiScoreSlope + mBand5GHzAward + mSameNetworkAward + + mSameBssidAward + mSecurityAward; + } + + private void localLog(String log) { + if (mLocalLog != null) { + mLocalLog.log(log); + } + } + + /** + * Get the evaluator name. + */ + public String getName() { + return NAME; + } + + /** + * Update all the saved networks' selection status + */ + private void updateSavedNetworkSelectionStatus() { + List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); + if (savedNetworks.size() == 0) { + localLog("No saved networks."); + return; + } + + StringBuffer sbuf = new StringBuffer("Saved Networks List: \n"); + for (WifiConfiguration network : savedNetworks) { + WifiConfiguration.NetworkSelectionStatus status = + network.getNetworkSelectionStatus(); + + // If a configuration is temporarily disabled, re-enable it before trying + // to connect to it. + mWifiConfigManager.tryEnableNetwork(network.networkId); + + //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED + // state. + + // Clear the cached candidate, score and seen. + mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); + + sbuf.append(" ").append(WifiNetworkSelector.toNetworkString(network)).append(" ") + .append(" User Preferred BSSID: ").append(network.BSSID) + .append(" FQDN: ").append(network.FQDN).append(" ") + .append(status.getNetworkStatusString()).append(" Disable account: "); + for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; + index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; + index++) { + sbuf.append(status.getDisableReasonCounter(index)).append(" "); + } + sbuf.append("Connect Choice: ").append(status.getConnectChoice()) + .append(" set time: ").append(status.getConnectChoiceTimestamp()) + .append("\n"); + } + localLog(sbuf.toString()); + } + + /** + * Update the evaluator. + */ + public void update(List<ScanDetail> scanDetails) { + updateSavedNetworkSelectionStatus(); + } + + private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, + WifiConfiguration currentNetwork, String currentBssid, + StringBuffer sbuf) { + int score = 0; + + sbuf.append("[ ").append(scanResult).append("] "); + // Calculate the RSSI score. + int rssi = scanResult.level <= mThresholdSaturatedRssi24 + ? scanResult.level : mThresholdSaturatedRssi24; + score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; + sbuf.append(" RSSI score: ").append(score).append(","); + + // 5GHz band bonus. + if (scanResult.is5GHz()) { + score += mBand5GHzAward; + sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward) + .append(","); + } + + // Last user selection award. + int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork(); + WifiConfiguration lastUserSelectedNetwork = + mWifiConfigManager.getConfiguredNetwork(lastUserSelectedNetworkId); + if (lastUserSelectedNetwork != null && lastUserSelectedNetworkId == network.networkId) { + long timeDifference = mClock.getElapsedSinceBootMillis() + - mWifiConfigManager.getLastSelectedTimeStamp(); + if (timeDifference > 0) { + int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); + score += bonus > 0 ? bonus : 0; + sbuf.append(" User selected it last time ").append(timeDifference / 1000 / 60) + .append(" minutes ago, bonus: ").append(bonus).append(","); + } + } + + // Same network award. + if (currentNetwork != null + && (network == currentNetwork || network.isLinked(currentNetwork))) { + score += mSameNetworkAward; + sbuf.append(" Same network bonus as the current one bonus: ") + .append(mSameNetworkAward).append(","); + } + + // Same BSSID award. + if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) { + score += mSameBssidAward; + sbuf.append(" Same BSSID as the current one bonus: ").append(mSameBssidAward) + .append(","); + } + + // Security award. + if (network.isPasspoint()) { + score += mPasspointSecurityAward; + sbuf.append(" Passpoint bonus: ").append(mPasspointSecurityAward).append(","); + } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) { + score += mSecurityAward; + sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(","); + } + + // No internet penalty. + if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) { + score -= mNoInternetPenalty; + sbuf.append(" No internet penalty: -").append(mNoInternetPenalty).append(","); + } + + sbuf.append(" ## Total score: ").append(score).append("\n"); + + return score; + } + + private WifiConfiguration adjustCandidateWithUserSelection(WifiConfiguration candidate, + ScanResult scanResultCandidate) { + WifiConfiguration tempConfig = candidate; + + while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { + String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); + tempConfig = mWifiConfigManager.getConfiguredNetwork(key); + + if (tempConfig != null) { + WifiConfiguration.NetworkSelectionStatus tempStatus = + tempConfig.getNetworkSelectionStatus(); + if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) { + scanResultCandidate = tempStatus.getCandidate(); + candidate = tempConfig; + } + } else { + localLog("Connect choice: " + key + " has no corresponding saved config."); + break; + } + } + localLog("After user selection adjustment, the final candidate is:" + + WifiNetworkSelector.toNetworkString(candidate) + " : " + + scanResultCandidate.BSSID); + return candidate; + } + + /** + * Evaluate all the networks from the scan results and return + * the WifiConfiguration of the network chosen for connection. + * + * @return configuration of the chosen network; + * null if no network in this category is available. + */ + public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, + WifiConfiguration currentNetwork, String currentBssid, boolean connected, + boolean untrustedNetworkAllowed, + List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) { + int highestScore = Integer.MIN_VALUE; + ScanResult scanResultCandidate = null; + WifiConfiguration candidate = null; + StringBuffer scoreHistory = new StringBuffer(); + + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + int highestScoreOfScanResult = Integer.MIN_VALUE; + int score; + int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID; + + // One ScanResult can be associated with more than one networks, hence we calculate all + // the scores and use the highest one as the ScanResult's score. + // TODO(b/31065385): WifiConfigManager does not support passpoint networks currently. + // So this list has just one entry always. + List<WifiConfiguration> associatedConfigurations = null; + WifiConfiguration associatedConfiguration = + mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); + + if (associatedConfiguration == null) { + continue; + } else { + associatedConfigurations = + new ArrayList<>(Arrays.asList(associatedConfiguration)); + } + + for (WifiConfiguration network : associatedConfigurations) { + WifiConfiguration.NetworkSelectionStatus status = + network.getNetworkSelectionStatus(); + status.setSeenInLastQualifiedNetworkSelection(true); + + if (!status.isNetworkEnabled()) { + continue; + } else if (network.BSSID != null && !network.BSSID.equals("any") + && !network.BSSID.equals(scanResult.BSSID)) { + // App has specified the only BSSID to connect for this + // configuration. So only the matching ScanResult can be a candidate. + localLog("Network " + WifiNetworkSelector.toNetworkString(network) + + " has specified BSSID " + network.BSSID + ". Skip " + + scanResult.BSSID); + continue; + } + + // If the network is marked to use external scores, leave it to the + // external score evaluator to handle it. + if (network.useExternalScores) { + localLog("Network " + WifiNetworkSelector.toNetworkString(network) + + " has external score."); + continue; + } + + score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid, + scoreHistory); + + if (score > highestScoreOfScanResult) { + highestScoreOfScanResult = score; + candidateIdOfScanResult = network.networkId; + } + + if (score > status.getCandidateScore() || (score == status.getCandidateScore() + && status.getCandidate() != null + && scanResult.level > status.getCandidate().level)) { + mWifiConfigManager.setNetworkCandidateScanResult( + candidateIdOfScanResult, scanResult, score); + } + } + + if (connectableNetworks != null) { + connectableNetworks.add(Pair.create(scanDetail, + mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult))); + } + + if (highestScoreOfScanResult > highestScore + || (highestScoreOfScanResult == highestScore + && scanResultCandidate != null + && scanResult.level > scanResultCandidate.level)) { + highestScore = highestScoreOfScanResult; + scanResultCandidate = scanResult; + mWifiConfigManager.setNetworkCandidateScanResult( + candidateIdOfScanResult, scanResultCandidate, highestScore); + // Reload the network config with the updated info. + candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult); + } + } + + if (scoreHistory.length() > 0) { + localLog("\n" + scoreHistory.toString()); + } + + if (scanResultCandidate != null) { + return adjustCandidateWithUserSelection(candidate, scanResultCandidate); + } else { + localLog("did not see any good candidates."); + return null; + } + } +} diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java index d570f81ec..3c2971210 100644 --- a/service/java/com/android/server/wifi/WifiConnectivityManager.java +++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java @@ -18,7 +18,6 @@ package com.android.server.wifi; import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE; -import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.net.wifi.ScanResult; @@ -37,8 +36,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.ScanResultUtil; -import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; @@ -50,7 +47,8 @@ import java.util.Set; * * When the screen is turned on or off, WiFi is connected or disconnected, * or on-demand, a scan is initiatiated and the scan results are passed - * to QNS for it to make a recommendation on which network to connect to. + * to WifiNetworkSelector for it to make a recommendation on which network + * to connect to. */ public class WifiConnectivityManager { public static final String WATCHDOG_TIMER_TAG = @@ -83,8 +81,8 @@ public class WifiConnectivityManager { // PNO scan interval in milli-seconds. This is the scan // performed when screen is off and connected. private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds - // When a network is found by PNO scan but gets rejected by QNS due to its - // low RSSI value, scan will be reschduled in an exponential back off manner. + // When a network is found by PNO scan but gets rejected by Wifi Network Selector due + // to its low RSSI value, scan will be reschduled in an exponential back off manner. private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds // Maximum number of retries when starting a scan failed @@ -118,24 +116,21 @@ public class WifiConnectivityManager { public static final int WIFI_STATE_DISCONNECTED = 2; public static final int WIFI_STATE_TRANSITIONING = 3; - // Due to b/28020168, timer based single scan will be scheduled - // to provide periodic scan in an exponential backoff fashion. - private static final boolean ENABLE_BACKGROUND_SCAN = false; - // Flag to turn on connected PNO, when needed - private static final boolean ENABLE_CONNECTED_PNO_SCAN = false; + // Saved network evaluator priority + private static final int SAVED_NETWORK_EVALUATOR_PRIORITY = 1; + private static final int EXTERNAL_SCORE_EVALUATOR_PRIORITY = 2; private final WifiStateMachine mStateMachine; private final WifiScanner mScanner; private final WifiConfigManager mConfigManager; private final WifiInfo mWifiInfo; - private final WifiQualifiedNetworkSelector mQualifiedNetworkSelector; + private final WifiNetworkSelector mNetworkSelector; private final WifiLastResortWatchdog mWifiLastResortWatchdog; private final WifiMetrics mWifiMetrics; private final AlarmManager mAlarmManager; private final Handler mEventHandler; private final Clock mClock; - private final LocalLog mLocalLog = - new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256); + private final LocalLog mLocalLog; private final LinkedList<Long> mConnectionAttemptTimeStamps; private boolean mDbg = false; @@ -166,7 +161,9 @@ public class WifiConnectivityManager { // A helper to log debugging information in the local log buffer, which can // be retrieved in bugreport. private void localLog(String log) { - mLocalLog.log(log); + if (mLocalLog != null) { + mLocalLog.log(log); + } } // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times @@ -216,21 +213,28 @@ public class WifiConnectivityManager { * Executes selection of potential network candidates, initiation of connection attempt to that * network. * - * @return true - if a candidate is selected by QNS - * false - if no candidate is selected by QNS + * @return true - if a candidate is selected by WifiNetworkSelector + * false - if no candidate is selected by WifiNetworkSelector */ private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) { - localLog(listenerName + " onResults: start QNS"); + if (mStateMachine.isLinkDebouncing() || mStateMachine.isSupplicantTransientState()) { + localLog(listenerName + " onResults: No network selection because linkDebouncing is " + + mStateMachine.isLinkDebouncing() + " and supplicantTransient is " + + mStateMachine.isSupplicantTransientState()); + return false; + } + + localLog(listenerName + " onResults: start network selection"); + WifiConfiguration candidate = - mQualifiedNetworkSelector.selectQualifiedNetwork(false, - mUntrustedConnectionAllowed, mStateMachine.isLinkDebouncing(), + mNetworkSelector.selectNetwork(scanDetails, mStateMachine.isConnected(), mStateMachine.isDisconnected(), - mStateMachine.isSupplicantTransientState(), scanDetails); + mUntrustedConnectionAllowed); mWifiLastResortWatchdog.updateAvailableNetworks( - mQualifiedNetworkSelector.getFilteredScanDetails()); + mNetworkSelector.getFilteredScanDetails()); mWifiMetrics.countScanResults(scanDetails); if (candidate != null) { - localLog(listenerName + ": QNS candidate-" + candidate.SSID); + localLog(listenerName + ": WNS candidate-" + candidate.SSID); connectToNetwork(candidate); return true; } else { @@ -238,63 +242,6 @@ public class WifiConnectivityManager { } } - // Periodic scan results listener. A periodic scan is initiated when - // screen is on. - private class PeriodicScanListener implements WifiScanner.ScanListener { - private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); - - public void clearScanDetails() { - mScanDetails.clear(); - } - - @Override - public void onSuccess() { - localLog("PeriodicScanListener onSuccess"); - } - - @Override - public void onFailure(int reason, String description) { - Log.e(TAG, "PeriodicScanListener onFailure:" - + " reason: " + reason - + " description: " + description); - - // reschedule the scan - if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { - scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS); - } else { - mScanRestartCount = 0; - Log.e(TAG, "Failed to successfully start periodic scan for " - + MAX_SCAN_RESTART_ALLOWED + " times"); - } - } - - @Override - public void onPeriodChanged(int periodInMs) { - localLog("PeriodicScanListener onPeriodChanged: " - + "actual scan period " + periodInMs + "ms"); - } - - @Override - public void onResults(WifiScanner.ScanData[] results) { - handleScanResults(mScanDetails, "PeriodicScanListener"); - clearScanDetails(); - mScanRestartCount = 0; - } - - @Override - public void onFullResult(ScanResult fullScanResult) { - if (mDbg) { - localLog("PeriodicScanListener onFullResult: " - + fullScanResult.SSID + " capabilities " - + fullScanResult.capabilities); - } - - mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult)); - } - } - - private final PeriodicScanListener mPeriodicScanListener = new PeriodicScanListener(); - // All single scan results listener. // // Note: This is the listener for all the available single scan results, @@ -365,7 +312,7 @@ public class WifiConnectivityManager { private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener(); // Single scan results listener. A single scan is initiated when - // Disconnected/ConnectedPNO scan found a valid network and woke up + // DisconnectedPNO scan found a valid network and woke up // the system, or by the watchdog timer, or to form the timer based // periodic scan. // @@ -414,9 +361,6 @@ public class WifiConnectivityManager { } } - // re-enable this when b/27695292 is fixed - // private final SingleScanListener mSingleScanListener = new SingleScanListener(); - // PNO scan results listener for both disconected and connected PNO scanning. // A PNO scan is initiated when screen is off. private class PnoScanListener implements WifiScanner.PnoScanListener { @@ -429,7 +373,7 @@ public class WifiConnectivityManager { } // Reset to the start value when either a non-PNO scan is started or - // QNS selects a candidate from the PNO scan results. + // WifiNetworkSelector selects a candidate from the PNO scan results. public void resetLowRssiNetworkRetryDelay() { mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS; } @@ -467,7 +411,7 @@ public class WifiConnectivityManager { } // Currently the PNO scan results doesn't include IE, - // which contains information required by QNS. Ignore them + // which contains information required by WifiNetworkSelector. Ignore them // for now. @Override public void onResults(WifiScanner.ScanData[] results) { @@ -491,7 +435,7 @@ public class WifiConnectivityManager { mScanRestartCount = 0; if (!wasConnectAttempted) { - // The scan results were rejected by QNS due to low RSSI values + // The scan results were rejected by WifiNetworkSelector due to low RSSI values if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) { mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS; } @@ -510,15 +454,16 @@ public class WifiConnectivityManager { /** * WifiConnectivityManager constructor */ - public WifiConnectivityManager(Context context, WifiStateMachine stateMachine, + WifiConnectivityManager(Context context, WifiStateMachine stateMachine, WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo, - WifiQualifiedNetworkSelector qualifiedNetworkSelector, - WifiInjector wifiInjector, Looper looper, boolean enable) { + WifiNetworkSelector networkSelector, WifiInjector wifiInjector, Looper looper, + boolean enable) { mStateMachine = stateMachine; mScanner = scanner; mConfigManager = configManager; mWifiInfo = wifiInfo; - mQualifiedNetworkSelector = qualifiedNetworkSelector; + mNetworkSelector = networkSelector; + mLocalLog = networkSelector.getLocalLog(); mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog(); mWifiMetrics = wifiInjector.getWifiMetrics(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); @@ -526,10 +471,12 @@ public class WifiConnectivityManager { mClock = wifiInjector.getClock(); mConnectionAttemptTimeStamps = new LinkedList<>(); - mMin5GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI; - mMin24GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI; - mBand5GHzBonus = WifiQualifiedNetworkSelector.BAND_AWARD_5GHz; - + mMin5GHzRssi = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); + mMin24GHzRssi = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); + mBand5GHzBonus = context.getResources().getInteger( + R.integer.config_wifi_framework_5GHz_preference_boost_factor); mCurrentConnectionBonus = context.getResources().getInteger( R.integer.config_wifi_framework_current_network_boost); mSameNetworkBonus = context.getResources().getInteger( @@ -538,11 +485,14 @@ public class WifiConnectivityManager { R.integer.config_wifi_framework_SECURITY_AWARD); int thresholdSaturatedRssi24 = context.getResources().getInteger( R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); - mInitialScoreMax = - (thresholdSaturatedRssi24 + WifiQualifiedNetworkSelector.RSSI_SCORE_OFFSET) - * WifiQualifiedNetworkSelector.RSSI_SCORE_SLOPE; mEnableAutoJoinWhenAssociated = context.getResources().getBoolean( R.bool.config_wifi_framework_enable_associated_network_selection); + mInitialScoreMax = (context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz) + + context.getResources().getInteger( + R.integer.config_wifi_framework_RSSI_SCORE_OFFSET)) + * context.getResources().getInteger( + R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); Log.i(TAG, "PNO settings:" + " min5GHzRssi " + mMin5GHzRssi + " min24GHzRssi " + mMin24GHzRssi @@ -551,6 +501,17 @@ public class WifiConnectivityManager { + " secureNetworkBonus " + mSecureBonus + " initialScoreMax " + mInitialScoreMax); + // Register the network evaluators + SavedNetworkEvaluator savedNetworkEvaluator = new SavedNetworkEvaluator(context, + mConfigManager, mClock, mLocalLog); + mNetworkSelector.registerNetworkEvaluator(savedNetworkEvaluator, + SAVED_NETWORK_EVALUATOR_PRIORITY); + + ExternalScoreEvaluator externalScoreEvaluator = new ExternalScoreEvaluator(context, + mConfigManager, mClock, mLocalLog); + mNetworkSelector.registerNetworkEvaluator(externalScoreEvaluator, + EXTERNAL_SCORE_EVALUATOR_PRIORITY); + // Register for all single scan results mScanner.registerScanListener(mAllSingleScanListener); @@ -601,7 +562,7 @@ public class WifiConnectivityManager { * Attempt to connect to a network candidate. * * Based on the currently connected network, this menthod determines whether we should - * connect or roam to the network candidate recommended by QNS. + * connect or roam to the network candidate recommended by WifiNetworkSelector. */ private void connectToNetwork(WifiConfiguration candidate) { ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate(); @@ -616,8 +577,8 @@ public class WifiConnectivityManager { // Check if we are already connected or in the process of connecting to the target // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just - // in case the firmware automatically roamed to a BSSID different from what QNS - // selected. + // in case the firmware automatically roamed to a BSSID different from what + // WifiNetworkSelector selected. if (targetBssid != null && (targetBssid.equals(mLastConnectionAttemptBssid) || targetBssid.equals(mWifiInfo.getBSSID())) @@ -703,7 +664,7 @@ public class WifiConnectivityManager { // Otherwise, the watchdog timer will be scheduled when entering disconnected // state. if (mWifiState == WIFI_STATE_DISCONNECTED) { - Log.i(TAG, "start a single scan from watchdogHandler"); + localLog("start a single scan from watchdogHandler"); scheduleWatchdogTimer(); startSingleScan(true); @@ -787,9 +748,6 @@ public class WifiConnectivityManager { settings.hiddenNetworks = hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[hiddenNetworkList.size()]); - // re-enable this when b/27695292 is fixed - // mSingleScanListener.clearScanDetails(); - // mScanner.startScan(settings, mSingleScanListener, WIFI_WORK_SOURCE); SingleScanListener singleScanListener = new SingleScanListener(isFullBandScan); mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE); @@ -806,23 +764,11 @@ public class WifiConnectivityManager { // Due to b/28020168, timer based single scan will be scheduled // to provide periodic scan in an exponential backoff fashion. - if (!ENABLE_BACKGROUND_SCAN) { - if (scanImmediately) { - resetLastPeriodicSingleScanTimeStamp(); - } - mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS; - startPeriodicSingleScan(); - } else { - ScanSettings settings = new ScanSettings(); - settings.band = getScanBand(); - settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT - | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; - settings.numBssidsPerScan = 0; - settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS; - - mPeriodicScanListener.clearScanDetails(); - mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE); + if (scanImmediately) { + resetLastPeriodicSingleScanTimeStamp(); } + mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS; + startPeriodicSingleScan(); } // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected @@ -856,8 +802,6 @@ public class WifiConnectivityManager { scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; scanSettings.numBssidsPerScan = 0; scanSettings.periodInMs = DISCONNECTED_PNO_SCAN_INTERVAL_MS; - // TODO: enable exponential back off scan later to further save energy - // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs; mPnoScanListener.clearScanDetails(); @@ -865,51 +809,7 @@ public class WifiConnectivityManager { mPnoScanStarted = true; } - // Start a ConnectedPNO scan when screen is off and Wifi is connected - private void startConnectedPnoScan() { - // TODO(b/29503772): Need to change this interface. - // Disable ConnectedPNO for now due to b/28020168 - if (!ENABLE_CONNECTED_PNO_SCAN) { - return; - } - - // Initialize PNO settings - PnoSettings pnoSettings = new PnoSettings(); - List<PnoSettings.PnoNetwork> pnoNetworkList = mConfigManager.retrievePnoNetworkList(); - int listSize = pnoNetworkList.size(); - - if (listSize == 0) { - // No saved network - localLog("No saved network for starting connected PNO."); - return; - } - - pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize]; - pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList); - pnoSettings.min5GHzRssi = mMin5GHzRssi; - pnoSettings.min24GHzRssi = mMin24GHzRssi; - pnoSettings.initialScoreMax = mInitialScoreMax; - pnoSettings.currentConnectionBonus = mCurrentConnectionBonus; - pnoSettings.sameNetworkBonus = mSameNetworkBonus; - pnoSettings.secureBonus = mSecureBonus; - pnoSettings.band5GHzBonus = mBand5GHzBonus; - - // Initialize scan settings - ScanSettings scanSettings = new ScanSettings(); - scanSettings.band = getScanBand(); - scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; - scanSettings.numBssidsPerScan = 0; - scanSettings.periodInMs = CONNECTED_PNO_SCAN_INTERVAL_MS; - // TODO: enable exponential back off scan later to further save energy - // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs; - - mPnoScanListener.clearScanDetails(); - - mScanner.startConnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener); - mPnoScanStarted = true; - } - - // Stop a PNO scan. This includes both DisconnectedPNO and ConnectedPNO scans. + // Stop PNO scan. private void stopPnoScan() { if (mPnoScanStarted) { mScanner.stopPnoScan(mPnoScanListener); @@ -920,7 +820,7 @@ public class WifiConnectivityManager { // Set up watchdog timer private void scheduleWatchdogTimer() { - Log.i(TAG, "scheduleWatchdogTimer"); + localLog("scheduleWatchdogTimer"); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS, @@ -994,9 +894,7 @@ public class WifiConnectivityManager { if (mScreenOn) { startPeriodicScan(scanImmediately); } else { // screenOff - if (mWifiState == WIFI_STATE_CONNECTED) { - startConnectedPnoScan(); - } else { + if (mWifiState == WIFI_STATE_DISCONNECTED) { startDisconnectedPnoScan(); } } @@ -1006,11 +904,7 @@ public class WifiConnectivityManager { private void stopConnectivityScan() { // Due to b/28020168, timer based single scan will be scheduled // to provide periodic scan in an exponential backoff fashion. - if (!ENABLE_BACKGROUND_SCAN) { - cancelPeriodicScanTimer(); - } else { - mScanner.stopBackgroundScan(mPeriodicScanListener); - } + cancelPeriodicScanTimer(); stopPnoScan(); mScanRestartCount = 0; } @@ -1048,7 +942,7 @@ public class WifiConnectivityManager { * Handler when user toggles whether untrusted connection is allowed */ public void setUntrustedConnectionAllowed(boolean allowed) { - Log.i(TAG, "setUntrustedConnectionAllowed: allowed=" + allowed); + localLog("setUntrustedConnectionAllowed: allowed=" + allowed); if (mUntrustedConnectionAllowed != allowed) { mUntrustedConnectionAllowed = allowed; @@ -1060,10 +954,9 @@ public class WifiConnectivityManager { * Handler when user specifies a particular network to connect to */ public void setUserConnectChoice(int netId) { - Log.i(TAG, "setUserConnectChoice: netId=" + netId); - - mQualifiedNetworkSelector.setUserConnectChoice(netId); + localLog("setUserConnectChoice: netId=" + netId); + mNetworkSelector.setUserConnectChoice(netId); clearConnectionAttemptTimeStamps(); } @@ -1071,24 +964,24 @@ public class WifiConnectivityManager { * Handler for on-demand connectivity scan */ public void forceConnectivityScan() { - Log.i(TAG, "forceConnectivityScan"); + localLog("forceConnectivityScan"); startConnectivityScan(SCAN_IMMEDIATELY); } /** - * Track whether a BSSID should be enabled or disabled for QNS + * Track whether a BSSID should be enabled or disabled for WifiNetworkSelector */ public boolean trackBssid(String bssid, boolean enable) { - Log.i(TAG, "trackBssid: " + (enable ? "enable " : "disable ") + bssid); + localLog("trackBssid: " + (enable ? "enable " : "disable ") + bssid); - boolean ret = mQualifiedNetworkSelector - .enableBssidForQualityNetworkSelection(bssid, enable); + boolean ret = mNetworkSelector + .enableBssidForNetworkSelection(bssid, enable); if (ret && !enable) { // Disabling a BSSID can happen when the AP candidate to connect to has - // no capacity for new stations. We start another scan immediately so that QNS - // can give us another candidate to connect to. + // no capacity for new stations. We start another scan immediately so that + // WifiNetworkSelector can give us another candidate to connect to. startConnectivityScan(SCAN_IMMEDIATELY); } @@ -1099,7 +992,7 @@ public class WifiConnectivityManager { * Inform WiFi is enabled for connection or not */ public void setWifiEnabled(boolean enable) { - Log.i(TAG, "Set WiFi " + (enable ? "enabled" : "disabled")); + localLog("Set WiFi " + (enable ? "enabled" : "disabled")); mWifiEnabled = enable; @@ -1108,7 +1001,7 @@ public class WifiConnectivityManager { resetLastPeriodicSingleScanTimeStamp(); mLastConnectionAttemptBssid = null; } else if (mWifiConnectivityManagerEnabled) { - startConnectivityScan(SCAN_IMMEDIATELY); + startConnectivityScan(SCAN_IMMEDIATELY); } } @@ -1116,7 +1009,7 @@ public class WifiConnectivityManager { * Turn on/off the WifiConnectivityMangager at runtime */ public void enable(boolean enable) { - Log.i(TAG, "Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled")); + localLog("Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled")); mWifiConnectivityManagerEnabled = enable; @@ -1125,29 +1018,10 @@ public class WifiConnectivityManager { resetLastPeriodicSingleScanTimeStamp(); mLastConnectionAttemptBssid = null; } else if (mWifiEnabled) { - startConnectivityScan(SCAN_IMMEDIATELY); + startConnectivityScan(SCAN_IMMEDIATELY); } } - /** - * Enable/disable verbose logging - */ - public void enableVerboseLogging(int verbose) { - mDbg = verbose > 0; - } - - /** - * Dump the local log buffer - */ - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("Dump of WifiConnectivityManager"); - pw.println("WifiConnectivityManager - Log Begin ----"); - pw.println("WifiConnectivityManager - Number of connectivity attempts rate limited: " - + mTotalConnectivityAttemptsRateLimited); - mLocalLog.dump(fd, pw, args); - pw.println("WifiConnectivityManager - Log End ----"); - } - @VisibleForTesting int getLowRssiNetworkRetryDelay() { return mPnoScanListener.getLowRssiNetworkRetryDelay(); diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java new file mode 100644 index 000000000..fec10eec7 --- /dev/null +++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2016 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.annotation.Nullable; +import android.app.ActivityManager; +import android.content.Context; +import android.net.NetworkKey; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * This class looks at all the connectivity scan results then + * selects a network for the phone to connect or roam to. + */ +public class WifiNetworkSelector { + private static final String TAG = "WifiNetworkSelector"; + private static final long INVALID_TIME_STAMP = Long.MIN_VALUE; + // Minimum time gap between last successful network selection and a new selection + // attempt. + @VisibleForTesting + public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000; + + // Constants for BSSID blacklist. + public static final int BSSID_BLACKLIST_THRESHOLD = 3; + public static final int BSSID_BLACKLIST_EXPIRE_TIME_MS = 5 * 60 * 1000; + + private WifiConfigManager mWifiConfigManager; + private WifiInfo mWifiInfo; + private Clock mClock; + private WifiConfiguration mCurrentNetwork = null; + private String mCurrentBssid = null; + private static class BssidBlacklistStatus { + // Number of times this BSSID has been rejected for association. + public int counter; + public boolean isBlacklisted; + public long blacklistedTimeStamp = INVALID_TIME_STAMP; + } + private Map<String, BssidBlacklistStatus> mBssidBlacklist = + new HashMap<>(); + + private final LocalLog mLocalLog = + new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512); + private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP; + // Buffer of filtered scan results (Scan results considered by network selection) & associated + // WifiConfiguration (if any). + private volatile List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks = + new ArrayList<>(); + private final int mThresholdQualifiedRssi24; + private final int mThresholdQualifiedRssi5; + private final int mThresholdMinimumRssi24; + private final int mThresholdMinimumRssi5; + private final boolean mEnableAutoJoinWhenAssociated; + + /** + * WiFi Network Selector supports various types of networks. Each type can + * have its evaluator to choose the best WiFi network for the device to connect + * to. When registering a WiFi network evaluator with the WiFi Network Selector, + * the priority of the network must be specified, and it must be a value between + * 0 and (EVALUATOR_MIN_PIRORITY - 1) with 0 being the highest priority. Wifi + * Network Selector iterates through the registered scorers from the highest priority + * to the lowest till a network is selected. + */ + public static final int EVALUATOR_MIN_PRIORITY = 6; + + /** + * Maximum number of evaluators can be registered with Wifi Network Selector. + */ + public static final int MAX_NUM_EVALUATORS = EVALUATOR_MIN_PRIORITY; + + /** + * Interface for WiFi Network Evaluator + * + * A network scorer evaulates all the networks from the scan results and + * recommends the best network in its category to connect or roam to. + */ + public interface NetworkEvaluator { + /** + * Get the evaluator name. + */ + String getName(); + + /** + * Update the evaluator. + * + * Certain evaluators have to be updated with the new scan results. For example + * the ExternalScoreEvalutor needs to refresh its Score Cache. + * + * @param scanDetails a list of scan details constructed from the scan results + */ + void update(List<ScanDetail> scanDetails); + + /** + * Evaluate all the networks from the scan results. + * + * @param scanDetails a list of scan details constructed from the scan results + * @param currentNetwork configuration of the current connected network + * or null if disconnected + * @param currentBssid BSSID of the current connected network or null if + * disconnected + * @param connected a flag to indicate if WifiStateMachine is in connected + * state + * @param untrustedNetworkAllowed a flag to indidate if untrusted networks like + * ephemeral networks are allowed + * @param connectableNetworks a list of the ScanDetail and WifiConfiguration + * pair which is used by the WifiLastResortWatchdog + * @return configuration of the chosen network; + * null if no network in this category is available. + */ + @Nullable + WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, + WifiConfiguration currentNetwork, String currentBssid, + boolean connected, boolean untrustedNetworkAllowed, + List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks); + } + + private final NetworkEvaluator[] mEvaluators = new NetworkEvaluator[MAX_NUM_EVALUATORS]; + + // A helper to log debugging information in the local log buffer, which can + // be retrieved in bugreport. + private void localLog(String log) { + mLocalLog.log(log); + } + + private boolean isCurrentNetworkSufficient(WifiConfiguration network) { + // Currently connected? + if (network == null) { + localLog("No current connected network."); + return false; + } else { + localLog("Current connected network: " + network.SSID + + " , ID: " + network.networkId); + } + + // Ephemeral network is not qualified. + if (network.ephemeral) { + localLog("Current network is an ephemeral one."); + return false; + } + + // Open network is not qualified. + if (WifiConfigurationUtil.isConfigForOpenNetwork(network)) { + localLog("Current network is a open one."); + return false; + } + + // 2.4GHz networks is not qualified. + if (mWifiInfo.is24GHz()) { + localLog("Current network is 2.4GHz."); + return false; + } + + // Is the current network's singnal strength qualified? It can only + // be a 5GHz network if we reach here. + int currentRssi = mWifiInfo.getRssi(); + if (mWifiInfo.is5GHz() && currentRssi < mThresholdQualifiedRssi5) { + localLog("Current network band=" + (mWifiInfo.is5GHz() ? "5GHz" : "2.4GHz") + + ", RSSI[" + currentRssi + "]-acceptable but not qualified."); + return false; + } + + return true; + } + + private boolean isNetworkSelectionNeeded(List<ScanDetail> scanDetails, + boolean connected, boolean disconnected) { + if (scanDetails.size() == 0) { + localLog("Empty connectivity scan results. Skip network selection."); + return false; + } + + if (connected) { + // Is roaming allowed? + if (!mEnableAutoJoinWhenAssociated) { + localLog("Switching networks in connected state is not allowed." + + " Skip network selection."); + return false; + } + + // Has it been at least the minimum interval since last network selection? + if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) { + long gap = mClock.getElapsedSinceBootMillis() + - mLastNetworkSelectionTimeStamp; + if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) { + localLog("Too short since last network selection: " + gap + " ms." + + " Skip network selection."); + return false; + } + } + + if (isCurrentNetworkSufficient(mCurrentNetwork)) { + localLog("Current connected network already sufficient. Skip network selection."); + return false; + } else { + localLog("Current connected network is not sufficient."); + } + } else if (disconnected) { + mCurrentNetwork = null; + mCurrentBssid = null; + } else { + // No network selection if WifiStateMachine is in a state other than + // CONNECTED or DISCONNECTED. + localLog("WifiStateMachine is in neither CONNECTED nor DISCONNECTED state." + + " Skip network selection."); + return false; + } + + return true; + } + + /** + * Format the given ScanResult as a scan ID for logging. + */ + public static String toScanId(@Nullable ScanResult scanResult) { + return scanResult == null ? "NULL" + : String.format("%s:%s", scanResult.SSID, scanResult.BSSID); + } + + /** + * Format the given WifiConfiguration as a SSID:netId string + */ + public static String toNetworkString(WifiConfiguration network) { + if (network == null) { + return null; + } + + return (network.SSID + ":" + network.networkId); + } + + private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails) { + ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>(); + List<ScanDetail> validScanDetails = new ArrayList<ScanDetail>(); + StringBuffer noValidSsid = new StringBuffer(); + StringBuffer blacklistedBssid = new StringBuffer(); + StringBuffer lowRssi = new StringBuffer(); + + for (ScanDetail scanDetail : scanDetails) { + ScanResult scanResult = scanDetail.getScanResult(); + + if (TextUtils.isEmpty(scanResult.SSID)) { + noValidSsid.append(scanResult.BSSID).append(" / "); + continue; + } + + final String scanId = toScanId(scanResult); + + if (isBssidDisabled(scanResult.BSSID)) { + blacklistedBssid.append(scanId).append(" / "); + continue; + } + + // Skip network with too weak signals. + if ((scanResult.is24GHz() && scanResult.level + < mThresholdMinimumRssi24) + || (scanResult.is5GHz() && scanResult.level + < mThresholdMinimumRssi5)) { + lowRssi.append(scanId).append("(") + .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz") + .append(")").append(scanResult.level).append(" / "); + continue; + } + + validScanDetails.add(scanDetail); + } + + if (noValidSsid.length() != 0) { + localLog("Networks filtered out due to invalid SSID: " + noValidSsid); + } + + if (blacklistedBssid.length() != 0) { + localLog("Networks filtered out due to blacklist: " + blacklistedBssid); + } + + if (lowRssi.length() != 0) { + localLog("Networks filtered out due to low signal strength: " + lowRssi); + } + + return validScanDetails; + } + + /** + * @return the list of ScanDetails scored as potential candidates by the last run of + * selectNetwork, this will be empty if Network selector determined no selection was + * needed on last run. This includes scan details of sufficient signal strength, and + * had an associated WifiConfiguration. + */ + public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() { + return mConnectableNetworks; + } + + /** + * This API is called when user explicitly selects a network. Currently, it is used in following + * cases: + * (1) User explicitly chooses to connect to a saved network. + * (2) User saves a network after adding a new network. + * (3) User saves a network after modifying a saved network. + * Following actions will be triggered: + * 1. If this network is disabled, we need re-enable it again. + * 2. This network is favored over all the other networks visible in latest network + * selection procedure. + * + * @param netId ID for the network chosen by the user + * @return true -- There is change made to connection choice of any saved network. + * false -- There is no change made to connection choice of any saved network. + */ + public boolean setUserConnectChoice(int netId) { + localLog("userSelectNetwork: network ID=" + netId); + WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId); + + if (selected == null || selected.SSID == null) { + localLog("userSelectNetwork: Invalid configuration with nid=" + netId); + return false; + } + + // Enable the network if it is disabled. + if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) { + mWifiConfigManager.updateNetworkSelectionStatus(netId, + WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE); + } + + boolean change = false; + String key = selected.configKey(); + // This is only used for setting the connect choice timestamp for debugging purposes. + long currentTime = mClock.getWallClockMillis(); + List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); + + for (WifiConfiguration network : savedNetworks) { + WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); + if (network.networkId == selected.networkId) { + if (status.getConnectChoice() != null) { + localLog("Remove user selection preference of " + status.getConnectChoice() + + " Set Time: " + status.getConnectChoiceTimestamp() + " from " + + network.SSID + " : " + network.networkId); + mWifiConfigManager.clearNetworkConnectChoice(network.networkId); + change = true; + } + continue; + } + + if (status.getSeenInLastQualifiedNetworkSelection() + && (status.getConnectChoice() == null + || !status.getConnectChoice().equals(key))) { + localLog("Add key: " + key + " Set Time: " + currentTime + " to " + + toNetworkString(network)); + mWifiConfigManager.setNetworkConnectChoice(network.networkId, key, currentTime); + change = true; + } + } + + return change; + } + + /** + * Enable/disable a BSSID for Network Selection + * When an association rejection event is obtained, Network Selector will disable this + * BSSID but supplicant still can try to connect to this bssid. If supplicant connect to it + * successfully later, this bssid can be re-enabled. + * + * @param bssid the bssid to be enabled / disabled + * @param enable -- true enable a bssid if it has been disabled + * -- false disable a bssid + */ + public boolean enableBssidForNetworkSelection(String bssid, boolean enable) { + if (enable) { + return (mBssidBlacklist.remove(bssid) != null); + } else { + if (bssid != null) { + BssidBlacklistStatus status = mBssidBlacklist.get(bssid); + if (status == null) { + // First time for this BSSID + BssidBlacklistStatus newStatus = new BssidBlacklistStatus(); + newStatus.counter++; + mBssidBlacklist.put(bssid, newStatus); + } else if (!status.isBlacklisted) { + status.counter++; + if (status.counter >= BSSID_BLACKLIST_THRESHOLD) { + status.isBlacklisted = true; + status.blacklistedTimeStamp = mClock.getElapsedSinceBootMillis(); + return true; + } + } + } + } + return false; + } + + /** + * Update the BSSID blacklist + * + * Go through the BSSID blacklist and check when a BSSID was blocked. If it + * has been blacklisted for BSSID_BLACKLIST_EXPIRE_TIME_MS, then re-enable it. + */ + private void updateBssidBlacklist() { + Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator(); + while (iter.hasNext()) { + BssidBlacklistStatus status = iter.next(); + if (status != null && status.isBlacklisted) { + if (mClock.getElapsedSinceBootMillis() - status.blacklistedTimeStamp + >= BSSID_BLACKLIST_EXPIRE_TIME_MS) { + iter.remove(); + } + } + } + } + + /** + * Check whether a bssid is disabled + * @param bssid -- the bssid to check + */ + private boolean isBssidDisabled(String bssid) { + BssidBlacklistStatus status = mBssidBlacklist.get(bssid); + return status == null ? false : status.isBlacklisted; + } + + /** + * + */ + @Nullable + public WifiConfiguration selectNetwork(List<ScanDetail> scanDetails, + boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) { + mConnectableNetworks.clear(); + if (scanDetails.size() == 0) { + localLog("Empty connectivity scan result"); + return null; + } + + if (mCurrentNetwork == null) { + mCurrentNetwork = + mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()); + } + + // Always get the current BSSID from WifiInfo in case that firmware initiated + // roaming happened. + mCurrentBssid = mWifiInfo.getBSSID(); + + // Shall we start network selection at all? + if (!isNetworkSelectionNeeded(scanDetails, connected, disconnected)) { + return null; + } + + // Update the registered network evaluators. + for (NetworkEvaluator registeredEvaluator : mEvaluators) { + if (registeredEvaluator != null) { + registeredEvaluator.update(scanDetails); + } + } + + // Check if any BSSID can be freed from the blacklist. + updateBssidBlacklist(); + + // Filter out unwanted networks. + List<ScanDetail> filteredScanDetails = filterScanResults(scanDetails); + if (filteredScanDetails.size() == 0) { + return null; + } + + // Go through the registered network evaluators from the highest priority + // one to the lowest till a network is selected. + WifiConfiguration selectedNetwork = null; + for (NetworkEvaluator registeredEvaluator : mEvaluators) { + if (registeredEvaluator != null) { + selectedNetwork = registeredEvaluator.evaluateNetworks(scanDetails, + mCurrentNetwork, mCurrentBssid, connected, + untrustedNetworkAllowed, mConnectableNetworks); + if (selectedNetwork != null) { + break; + } + } + } + + if (selectedNetwork != null) { + mCurrentNetwork = selectedNetwork; + mCurrentBssid = selectedNetwork.getNetworkSelectionStatus().getCandidate().BSSID; + mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis(); + } + + return selectedNetwork; + } + + /** + * Register a network evaluator + * + * @param evaluator the network evaluator to be registered + * @param priority a value between 0 and (SCORER_MIN_PRIORITY-1) + * + * @return true if the evaluator is successfully registered with QNS; + * false if failed to register the evaluator + */ + public boolean registerNetworkEvaluator(NetworkEvaluator evaluator, int priority) { + if (priority < 0 || priority >= EVALUATOR_MIN_PRIORITY) { + Log.e(TAG, "Invalid network evaluator priority: " + priority); + return false; + } + + if (mEvaluators[priority] != null) { + Log.e(TAG, "Priority " + priority + " is already registered by " + + mEvaluators[priority].getName()); + return false; + } + + mEvaluators[priority] = evaluator; + return true; + } + + /** + * Unregister a network evaluator + * + * @param evaluator the network evaluator to be unregistered from QNS + * + * @return true if the evaluator is successfully unregistered from; + * false if failed to unregister the evaluator + */ + public boolean unregisterNetworkEvaluator(NetworkEvaluator evaluator) { + for (NetworkEvaluator registeredEvaluator : mEvaluators) { + if (registeredEvaluator == evaluator) { + Log.d(TAG, "Unregistered network evaluator: " + evaluator.getName()); + return true; + } + } + + Log.e(TAG, "Couldn't unregister network evaluator: " + evaluator.getName()); + return false; + } + + WifiNetworkSelector(Context context, WifiConfigManager configManager, + WifiInfo wifiInfo, Clock clock) { + mWifiConfigManager = configManager; + mWifiInfo = wifiInfo; + mClock = clock; + + mThresholdQualifiedRssi24 = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz); + mThresholdQualifiedRssi5 = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz); + mThresholdMinimumRssi24 = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); + mThresholdMinimumRssi5 = context.getResources().getInteger( + R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); + mEnableAutoJoinWhenAssociated = context.getResources().getBoolean( + R.bool.config_wifi_framework_enable_associated_network_selection); + } + + /** + * Retrieve the local log buffer created by WifiNetworkSelector. + */ + public LocalLog getLocalLog() { + return mLocalLog; + } + + /** + * Dump the local logs. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Dump of WifiNetworkSelector"); + pw.println("WifiNetworkSelector - Log Begin ----"); + mLocalLog.dump(fd, pw, args); + pw.println("WifiNetworkSelector - Log End ----"); + } +} diff --git a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java deleted file mode 100644 index d920273f4..000000000 --- a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java +++ /dev/null @@ -1,1062 +0,0 @@ -/* - * Copyright (C) 2015 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.annotation.Nullable; -import android.content.Context; -import android.net.NetworkKey; -import android.net.NetworkScoreManager; -import android.net.WifiKey; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Process; -import android.text.TextUtils; -import android.util.LocalLog; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wifi.util.ScanResultUtil; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * This class looks at all the connectivity scan results then - * selects a network for the phone to connect or roam to. - */ -public class WifiQualifiedNetworkSelector { - private WifiConfigManager mWifiConfigManager; - private WifiInfo mWifiInfo; - private NetworkScoreManager mScoreManager; - private WifiNetworkScoreCache mNetworkScoreCache; - private Clock mClock; - private static final String TAG = "WifiQualifiedNetworkSelector:"; - // Always enable debugging logs for now since QNS is still a new feature. - private static final boolean FORCE_DEBUG = true; - private boolean mDbg = FORCE_DEBUG; - private WifiConfiguration mCurrentConnectedNetwork = null; - private String mCurrentBssid = null; - - // Buffer of filtered scan results (Scan results considered by network selection) & associated - // WifiConfiguration (if any). - private volatile List<Pair<ScanDetail, WifiConfiguration>> mFilteredScanDetails = null; - - // Minimum time gap between last successful Qualified Network Selection and a new selection - // attempt. - private static final int MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000; - - // A 2.4GHz network with RSSI value above this threshold is considered qualified. No new - // selection attempt necessary. - public static final int QUALIFIED_RSSI_24G_BAND = -73; - // A 5GHz network with RSSI value above this threshold is considered qualified. No new - // selection attempt necessary. - public static final int QUALIFIED_RSSI_5G_BAND = -70; - // A RSSI vaule larger than this threshold is considered saturated and switching to a - // higher RSSI value network won't benefit the connection much. - public static final int RSSI_SATURATION_2G_BAND = -60; - public static final int RSSI_SATURATION_5G_BAND = -57; - // Any RSSI value below this is considered unacceptable, and the network will be filtered out. - public static final int MINIMUM_2G_ACCEPT_RSSI = -85; - public static final int MINIMUM_5G_ACCEPT_RSSI = -82; - - // Constants for BSSID scoring formula. - public static final int RSSI_SCORE_SLOPE = 4; - public static final int RSSI_SCORE_OFFSET = 85; - public static final int BAND_AWARD_5GHz = 40; - public static final int SAME_NETWORK_AWARD = 16; - public static final int SAME_BSSID_AWARD = 24; - public static final int LAST_SELECTION_AWARD = 480; - public static final int PASSPOINT_SECURITY_AWARD = 40; - public static final int SECURITY_AWARD = 80; - - // BSSID blacklist parameters. - public static final int BSSID_BLACKLIST_THRESHOLD = 3; - public static final int BSSID_BLACKLIST_EXPIRE_TIME_MS = 5 * 60 * 1000; - - private final int mNoIntnetPenalty; - private static final int INVALID_TIME_STAMP = -1; - private long mLastQualifiedNetworkSelectionTimeStamp = INVALID_TIME_STAMP; - - private final LocalLog mLocalLog = new LocalLog(512); - private int mRssiScoreSlope = RSSI_SCORE_SLOPE; - private int mRssiScoreOffset = RSSI_SCORE_OFFSET; - private int mSameBssidAward = SAME_BSSID_AWARD; - private int mLastSelectionAward = LAST_SELECTION_AWARD; - private int mPasspointSecurityAward = PASSPOINT_SECURITY_AWARD; - private int mSecurityAward = SECURITY_AWARD; - private final int mThresholdQualifiedRssi24; - private final int mThresholdQualifiedRssi5; - private final boolean mEnableAutoJoinWhenAssociated; - private final int mBandAward5Ghz; - private final int mCurrentNetworkBoost; - private final int mThresholdSaturatedRssi24; - private final int mThresholdSaturatedRssi5; - private final int mThresholdMinimumRssi5; - private final int mThresholdMinimumRssi24; - private int mUserPreferedBand = WifiManager.WIFI_FREQUENCY_BAND_AUTO; - private Map<String, BssidBlacklistStatus> mBssidBlacklist = - new HashMap<String, BssidBlacklistStatus>(); - - /** - * Class that saves the blacklist status of a given BSSID. - */ - private static class BssidBlacklistStatus { - // Number of times this BSSID has been requested to be blacklisted. - // Association rejection triggers such a request. - int mCounter; - boolean mIsBlacklisted; - long mBlacklistedTimeStamp = INVALID_TIME_STAMP; - } - - private void localLog(String log) { - if (mDbg) { - mLocalLog.log(log); - } - } - - private void localLoge(String log) { - mLocalLog.log(log); - } - - @VisibleForTesting - void setWifiNetworkScoreCache(WifiNetworkScoreCache cache) { - mNetworkScoreCache = cache; - } - - /** - * @return current target connected network - */ - public WifiConfiguration getConnetionTargetNetwork() { - return mCurrentConnectedNetwork; - } - - /** - * @return the list of ScanDetails scored as potential candidates by the last run of - * selectQualifiedNetwork, this will be empty if QNS determined no selection was needed on last - * run. This includes scan details of sufficient signal strength, and had an associated - * WifiConfiguration. - */ - public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() { - return mFilteredScanDetails; - } - - WifiQualifiedNetworkSelector(WifiConfigManager configManager, Context context, - WifiInfo wifiInfo, Clock clock) { - mWifiConfigManager = configManager; - mWifiInfo = wifiInfo; - mClock = clock; - mScoreManager = - (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE); - if (mScoreManager != null) { - mNetworkScoreCache = new WifiNetworkScoreCache(context); - mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache); - } else { - localLoge("Couldn't get NETWORK_SCORE_SERVICE."); - mNetworkScoreCache = null; - } - - mRssiScoreSlope = context.getResources().getInteger( - R.integer.config_wifi_framework_RSSI_SCORE_SLOPE); - mRssiScoreOffset = context.getResources().getInteger( - R.integer.config_wifi_framework_RSSI_SCORE_OFFSET); - mSameBssidAward = context.getResources().getInteger( - R.integer.config_wifi_framework_SAME_BSSID_AWARD); - mLastSelectionAward = context.getResources().getInteger( - R.integer.config_wifi_framework_LAST_SELECTION_AWARD); - mPasspointSecurityAward = context.getResources().getInteger( - R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD); - mSecurityAward = context.getResources().getInteger( - R.integer.config_wifi_framework_SECURITY_AWARD); - mThresholdQualifiedRssi24 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz); - mThresholdQualifiedRssi5 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz); - mEnableAutoJoinWhenAssociated = context.getResources().getBoolean( - R.bool.config_wifi_framework_enable_associated_network_selection); - mBandAward5Ghz = context.getResources().getInteger( - R.integer.config_wifi_framework_5GHz_preference_boost_factor); - mCurrentNetworkBoost = context.getResources().getInteger( - R.integer.config_wifi_framework_current_network_boost); - mThresholdSaturatedRssi24 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); - mThresholdSaturatedRssi5 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); - mThresholdMinimumRssi5 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); - mThresholdMinimumRssi24 = context.getResources().getInteger( - R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); - mNoIntnetPenalty = - (mThresholdSaturatedRssi24 + mRssiScoreOffset) * mRssiScoreSlope + - mBandAward5Ghz + mCurrentNetworkBoost + mSameBssidAward + mSecurityAward; - } - - void enableVerboseLogging(int verbose) { - mDbg = verbose > 0 || FORCE_DEBUG; - } - - private String getNetworkString(WifiConfiguration network) { - if (network == null) { - return null; - } - - return (network.SSID + ":" + network.networkId); - - } - - /** - * Check if the current connected network is already qualified so that network - * selection from the new scan results is not necessary. - * - * @param currentNetwork -- current connected network - */ - private boolean isCurrentNetworkQualified(WifiConfiguration currentNetwork) { - if (currentNetwork == null) { - localLog("No current connected network"); - return false; - } else { - localLog("Current connected network: " + currentNetwork.SSID - + " , ID: " + currentNetwork.networkId); - } - - // Ephemeral networks are not qualified. - if (currentNetwork.ephemeral) { - localLog("Current network is an ephemeral one"); - return false; - } - - // Open networks are not qualified. - if (WifiConfigurationUtil.isConfigForOpenNetwork(currentNetwork)) { - localLog("Current network is a open one"); - return false; - } - - // Does the current network band match the user preference? - // - // Note, here the check for 2.4GHz band is different from the one for 5GHz band - // such that 5GHz band is always favored. - // When the current network is 2.4GHz, it is considered as not qualified as long - // as the band preference set by user is not 2.4GHz only. This gives QNS an - // opportunity to recommend a 5GHz network if one is available. - // When the current network is 5GHz, it's considered as not qualified only if - // the band preference set by user is 2.4GHz only. - if ((mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ)) - || (mWifiInfo.is5GHz() - && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ))) { - localLog("Current network band does not match user preference: " - + "current network band=" + (mWifiInfo.is24GHz() ? "2.4GHz" : "5GHz") - + ", however user preferred band=" + mUserPreferedBand); - return false; - } - - // Is the current network's singnal strength qualified? - int currentRssi = mWifiInfo.getRssi(); - if ((mWifiInfo.is24GHz() && currentRssi < mThresholdQualifiedRssi24) - || (mWifiInfo.is5GHz() && currentRssi < mThresholdQualifiedRssi5)) { - localLog("Current network band=" + (mWifiInfo.is24GHz() ? "2.4GHz" : "5GHz") - + ", RSSI[" + currentRssi + "]-acceptable but not qualified"); - return false; - } - - return true; - } - - /** - * Check whether QualifiedNetworkSelection is needed. - * - * @param isLinkDebouncing true -- Link layer is under debouncing - * false -- Link layer is not under debouncing - * @param isConnected true -- device is connected to an AP currently - * false -- device is not connected to an AP currently - * @param isDisconnected true -- WifiStateMachine is at disconnected state - * false -- WifiStateMachine is not at disconnected state - * @param isSupplicantTransientState true -- supplicant is in a transient state now - * false -- supplicant is not in a transient state now - */ - private boolean needQualifiedNetworkSelection(boolean isLinkDebouncing, boolean isConnected, - boolean isDisconnected, boolean isSupplicantTransientState) { - // No Qualified Network Selection during the L2 link debouncing procedure. - if (isLinkDebouncing) { - localLog("No QNS during L2 debouncing"); - return false; - } - - if (isConnected) { - // Already connected. Looking for a better candidate. - - // Is network switching allowed in connected state? - if (!mEnableAutoJoinWhenAssociated) { - localLog("Switching networks in connected state is not allowed"); - return false; - } - - // Do not select again if last selection is within - // MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS. - if (mLastQualifiedNetworkSelectionTimeStamp != INVALID_TIME_STAMP) { - long gap = mClock.getElapsedSinceBootMillis() - - mLastQualifiedNetworkSelectionTimeStamp; - if (gap < MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL_MS) { - localLog("Too short from last successful Qualified Network Selection. Gap is:" - + gap + " ms!"); - return false; - } - } - - WifiConfiguration currentNetwork = - mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()); - if (currentNetwork == null) { - // WifiStateMachine in connected state but WifiInfo is not. It means there is a race - // condition. Defer QNS until WifiStateMachine enters the disconnected state. - // - // TODO(b/28249371): Root cause this race condition. - return false; - } - - // Already connected to a qualified network? - if (!isCurrentNetworkQualified(mCurrentConnectedNetwork)) { - localLog("Current connected network is not qualified"); - return true; - } else { - return false; - } - } else if (isDisconnected) { - mCurrentConnectedNetwork = null; - mCurrentBssid = null; - // Defer Qualified Network Selection if wpa_supplicant is in the transient state. - if (isSupplicantTransientState) { - return false; - } - } else { - // Do not allow new network selection if WifiStateMachine is in a state - // other than connected or disconnected. - localLog("WifiStateMachine is not on connected or disconnected state"); - return false; - } - - return true; - } - - int calculateBssidScore(ScanResult scanResult, WifiConfiguration network, - boolean sameNetwork, boolean sameBssid, boolean sameSelect, StringBuffer sbuf) { - - int score = 0; - // Calculate the RSSI score. - int rssi = scanResult.level <= mThresholdSaturatedRssi24 - ? scanResult.level : mThresholdSaturatedRssi24; - score += (rssi + mRssiScoreOffset) * mRssiScoreSlope; - sbuf.append("RSSI score: ").append(score); - - // 5GHz band bonus. - if (scanResult.is5GHz()) { - score += mBandAward5Ghz; - sbuf.append(" 5GHz bonus: ").append(mBandAward5Ghz); - } - - // Last user selection award. - if (sameSelect) { - long timeDifference = - mClock.getElapsedSinceBootMillis() - - mWifiConfigManager.getLastSelectedTimeStamp(); - if (timeDifference > 0) { - int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60); - score += bonus > 0 ? bonus : 0; - sbuf.append(" User selected it last time ").append(timeDifference / 1000 / 60) - .append(" minutes ago, bonus: ").append(bonus); - } - } - - // Same network award. - if (sameNetwork) { - score += mCurrentNetworkBoost; - sbuf.append(" Same network as the current one, bonus: ").append(mCurrentNetworkBoost); - } - - // Same BSSID award. - if (sameBssid) { - score += mSameBssidAward; - sbuf.append(" Same BSSID as the current one, bonus: ").append(mSameBssidAward); - } - - // Security award. - if (network.isPasspoint()) { - score += mPasspointSecurityAward; - sbuf.append(" Passpoint bonus: ").append(mPasspointSecurityAward); - } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) { - score += mSecurityAward; - sbuf.append(" Secure network bonus: ").append(mSecurityAward); - } - - // No internet penalty. - if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) { - score -= mNoIntnetPenalty; - sbuf.append(" No internet penalty: -").append(mNoIntnetPenalty); - } - - sbuf.append(" -- ScanResult: ").append(scanResult).append(" for network: ") - .append(network.networkId).append(" score: ").append(score).append(" --\n"); - - return score; - } - - /** - * Update all the saved networks' selection status - */ - private void updateSavedNetworkSelectionStatus() { - List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); - if (savedNetworks.size() == 0) { - localLog("no saved network"); - return; - } - - StringBuffer sbuf = new StringBuffer("Saved Network List: \n"); - for (WifiConfiguration network : savedNetworks) { - WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); - // If a configuration is temporarily disabled, re-enable it before trying - // to connect to it. - mWifiConfigManager.tryEnableNetwork(network.networkId); - - // Clear the cached candidate, score and seen. - mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); - - sbuf.append(" ").append(getNetworkString(network)).append(" ") - .append(" User Preferred BSSID: ").append(network.BSSID) - .append(" FQDN: ").append(network.FQDN).append(" ") - .append(status.getNetworkStatusString()).append(" Disable account: "); - for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE; - index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX; - index++) { - sbuf.append(status.getDisableReasonCounter(index)).append(" "); - } - sbuf.append("Connect Choice: ").append(status.getConnectChoice()) - .append(" set time: ").append(status.getConnectChoiceTimestamp()) - .append("\n"); - } - localLog(sbuf.toString()); - } - - /** - * This API is called when user/app explicitly selects a network. Currently, it is used in - * following cases: - * (1) User explicitly chooses to connect to a saved network. - * (2) User saves a network after adding a new network. - * (3) User saves a network after modifying a saved network. - * Following actions will be triggered: - * 1. If this network is disabled, we need re-enable it again. - * 2. This network is favored over all the other networks visible in latest network - * selection procedure. - * - * @param netId ID for the network chosen by the user - * @return true -- There is change made to connection choice of any saved network. - * false -- There is no change made to connection choice of any saved network. - */ - public boolean setUserConnectChoice(int netId) { - localLog("setUserConnectChoice: network ID=" + netId); - WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId); - if (selected == null || selected.SSID == null) { - localLoge("setUserConnectChoice: Invalid configuration with nid=" + netId); - return false; - } - - boolean change = false; - String key = selected.configKey(); - // This is only used for setting the connect choice timestamp for debugging purposes. - long currentTime = mClock.getWallClockMillis(); - List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks(); - for (WifiConfiguration network : savedNetworks) { - WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); - if (network.networkId == selected.networkId) { - if (status.getConnectChoice() != null) { - localLog("Clear connection choice of " + status.getConnectChoice() - + " Set Time: " + status.getConnectChoiceTimestamp() + " from " - + getNetworkString(network)); - mWifiConfigManager.clearNetworkConnectChoice(network.networkId); - change = true; - } - continue; - } - - if (status.getSeenInLastQualifiedNetworkSelection() - && (status.getConnectChoice() == null - || !status.getConnectChoice().equals(key))) { - localLog("Set connection choice of " + key + " Set Time: " + currentTime + " to " - + getNetworkString(network)); - mWifiConfigManager.setNetworkConnectChoice(network.networkId, key, currentTime); - change = true; - } - } - - return change; - } - - /** - * Enable/disable a BSSID for Quality Network Selection - * When an association rejection event is obtained, Quality Network Selector will disable this - * BSSID but supplicant still can try to connect to this bssid. If supplicant connect to it - * successfully later, this bssid can be re-enabled. - * - * @param bssid the bssid to be enabled / disabled - * @param enable -- true enable a bssid if it has been disabled - * -- false disable a bssid - */ - public boolean enableBssidForQualityNetworkSelection(String bssid, boolean enable) { - if (enable) { - return (mBssidBlacklist.remove(bssid) != null); - } else { - if (bssid != null) { - BssidBlacklistStatus status = mBssidBlacklist.get(bssid); - if (status == null) { - // First time for this BSSID - BssidBlacklistStatus newStatus = new BssidBlacklistStatus(); - newStatus.mCounter++; - mBssidBlacklist.put(bssid, newStatus); - } else if (!status.mIsBlacklisted) { - status.mCounter++; - if (status.mCounter >= BSSID_BLACKLIST_THRESHOLD) { - status.mIsBlacklisted = true; - status.mBlacklistedTimeStamp = mClock.getElapsedSinceBootMillis(); - return true; - } - } - } - } - return false; - } - - /** - * Update the buffered BSSID blacklist - * - * Go through the whole buffered BSSIDs blacklist and check when the BSSIDs is blocked. If they - * have been blacklisted for BSSID_BLACKLIST_EXPIRE_TIME_MS, re-enable them. - */ - private void updateBssidBlacklist() { - Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator(); - while (iter.hasNext()) { - BssidBlacklistStatus status = iter.next(); - if (status != null && status.mIsBlacklisted) { - if (mClock.getElapsedSinceBootMillis() - status.mBlacklistedTimeStamp - >= BSSID_BLACKLIST_EXPIRE_TIME_MS) { - iter.remove(); - } - } - } - } - - /** - * Check whether a bssid is disabled - * @param bssid -- the bssid to check - */ - public boolean isBssidDisabled(String bssid) { - BssidBlacklistStatus status = mBssidBlacklist.get(bssid); - return status == null ? false : status.mIsBlacklisted; - } - - /** - * Select the best network candidate from the new scan results for WifiConnectivityManager - * to connect/roam to. - * - * @param forceSelectNetwork true -- start a qualified network selection anyway, no matter - * the current network is already qualified or not. - * false -- if current network is already qualified, stay connected - * to it. - * @param isUntrustedConnectionsAllowed connection to untrusted networks is allowed or not - * @param isLinkDebouncing Link layer is under debouncing or not - * @param isConnected WifiStateMachine is in the Connected state or not - * @param isDisconnected WifiStateMachine is in the Disconnected state or not - * @param isSupplicantTransient wpa_supplicant is in a transient state or not - * @param scanDetails new connectivity scan results - * @return Best network candidate identified. Null if no candidate available or we should - * stay connected to the current network. - */ - public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork, - boolean isUntrustedConnectionsAllowed, boolean isLinkDebouncing, - boolean isConnected, boolean isDisconnected, boolean isSupplicantTransient, - List<ScanDetail> scanDetails) { - localLog("==========start qualified Network Selection=========="); - - List<Pair<ScanDetail, WifiConfiguration>> filteredScanDetails = new ArrayList<>(); - - if (scanDetails.size() == 0) { - localLog("Empty connectivity scan result"); - mFilteredScanDetails = filteredScanDetails; - return null; - } - - if (mCurrentConnectedNetwork == null) { - mCurrentConnectedNetwork = - mWifiConfigManager.getConfiguredNetwork(mWifiInfo.getNetworkId()); - } - - if (mCurrentBssid == null) { - mCurrentBssid = mWifiInfo.getBSSID(); - } - - if (!forceSelectNetwork && !needQualifiedNetworkSelection(isLinkDebouncing, isConnected, - isDisconnected, isSupplicantTransient)) { - localLog("Stay connected to the current qualified network"); - mFilteredScanDetails = filteredScanDetails; - return null; - } - - int currentHighestScore = Integer.MIN_VALUE; - ScanResult scanResultCandidate = null; - WifiConfiguration networkCandidate = null; - final ExternalScoreEvaluator externalScoreEvaluator = - new ExternalScoreEvaluator(mLocalLog, mDbg); - int lastUserSelectedNetWork = mWifiConfigManager.getLastSelectedNetwork(); - WifiConfiguration lastUserSelectedNetwork = - mWifiConfigManager.getConfiguredNetwork(lastUserSelectedNetWork); - if (lastUserSelectedNetwork != null) { - localLog("Last selection is " + lastUserSelectedNetwork.SSID + " Time to now: " - + ((mClock.getElapsedSinceBootMillis() - - mWifiConfigManager.getLastSelectedTimeStamp()) / 1000 / 60 + " minutes")); - } - - updateSavedNetworkSelectionStatus(); - updateBssidBlacklist(); - - StringBuffer lowSignalScan = new StringBuffer(); - StringBuffer notSavedScan = new StringBuffer(); - StringBuffer noValidSsid = new StringBuffer(); - StringBuffer unwantedBand = new StringBuffer(); - StringBuffer scoreHistory = new StringBuffer(); - ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>(); - - // Iterate over all scan results to find the best candidate. - for (ScanDetail scanDetail : scanDetails) { - ScanResult scanResult = scanDetail.getScanResult(); - // Skip bad scan result. - if (scanResult.SSID == null || TextUtils.isEmpty(scanResult.SSID)) { - if (mDbg) { - noValidSsid.append(scanResult.BSSID).append(" / "); - } - continue; - } - - final String scanId = toScanId(scanResult); - // Skip blacklisted BSSID. - if (isBssidDisabled(scanResult.BSSID)) { - Log.i(TAG, scanId + " is in the blacklist."); - continue; - } - - // Skip network with too weak signals. - if ((scanResult.is24GHz() && scanResult.level < mThresholdMinimumRssi24) - || (scanResult.is5GHz() && scanResult.level < mThresholdMinimumRssi5)) { - if (mDbg) { - lowSignalScan.append(scanId).append("(") - .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz") - .append(")").append(scanResult.level).append(" / "); - } - continue; - } - - // Skip network not matching band preference set by user. - // WifiConnectivityManager schedules scan according to the user band prefrence. This is - // a check for the ScanResults generated from the old settings. - if ((scanResult.is24GHz() - && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_5GHZ)) - || (scanResult.is5GHz() - && (mUserPreferedBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ))) { - if (mDbg) { - unwantedBand.append(scanId).append("(") - .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz") - .append(")").append(" / "); - } - continue; - } - - // Is there a score for this network? If not, request a score. - if (mNetworkScoreCache != null && !mNetworkScoreCache.isScoredNetwork(scanResult)) { - WifiKey wifiKey; - try { - wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID); - NetworkKey ntwkKey = new NetworkKey(wifiKey); - // Add to the unscoredNetworks list so we can request score later - unscoredNetworks.add(ntwkKey); - } catch (IllegalArgumentException e) { - Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID - + " for network score. Skip."); - } - } - - // Is this scan result from an ephemeral network? - boolean potentiallyEphemeral = false; - // Stores WifiConfiguration of potential connection candidates for scan result filtering - WifiConfiguration potentialEphemeralCandidate = null; - // TODO(b/31065385): WifiConfigManager does not support passpoint networks currently. - // So this list has just one entry always. - List<WifiConfiguration> associatedWifiConfigurations = null; - WifiConfiguration associatedWifiConfiguration = - mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); - if (associatedWifiConfiguration != null) { - associatedWifiConfigurations = - new ArrayList<>(Arrays.asList(associatedWifiConfiguration)); - } - if (associatedWifiConfigurations == null) { - potentiallyEphemeral = true; - if (mDbg) { - notSavedScan.append(scanId).append(" / "); - } - } else if (associatedWifiConfigurations.size() == 1) { - // If there is more than one associated network, it must be a passpoint network. - WifiConfiguration network = associatedWifiConfigurations.get(0); - if (network.ephemeral) { - potentialEphemeralCandidate = network; - potentiallyEphemeral = true; - } - } - - // Evaluate the potentially ephemeral network as a possible candidate if untrusted - // connections are allowed and we have an external score for the scan result. - if (potentiallyEphemeral) { - if (isUntrustedConnectionsAllowed) { - Integer netScore = getNetworkScore(scanResult, false); - if (netScore != null - && !mWifiConfigManager.wasEphemeralNetworkDeleted(scanResult.SSID)) { - externalScoreEvaluator.evalUntrustedCandidate(netScore, scanResult); - // scanDetail is for available ephemeral network - filteredScanDetails.add(Pair.create(scanDetail, - potentialEphemeralCandidate)); - } - } - continue; - } - - // Calculate the score of each ScanResult whose associated network is not ephemeral. - // One ScanResult can associated with more than one network, hence we calculate all - // the scores and use the highest one as the ScanResult's score - int highestScore = Integer.MIN_VALUE; - int score; - int candidateNetworkIdForThisScan = WifiConfiguration.INVALID_NETWORK_ID; - WifiConfiguration potentialCandidate = null; - for (WifiConfiguration network : associatedWifiConfigurations) { - WifiConfiguration.NetworkSelectionStatus status = - network.getNetworkSelectionStatus(); - if (potentialCandidate == null) { - potentialCandidate = network; - } - if (!status.isNetworkEnabled()) { - continue; - } else if (network.BSSID != null && !network.BSSID.equals("any") - && !network.BSSID.equals(scanResult.BSSID)) { - // App has specified the only BSSID to connect for this - // configuration. So only the matching ScanResult can be a candidate. - localLog("Network " + getNetworkString(network) + " has specified BSSID " - + network.BSSID + ". Skip " + scanResult.BSSID); - continue; - } - - // If the network is marked to use external scores then attempt to fetch the score. - // These networks will not be considered alongside the other saved networks. - if (network.useExternalScores) { - Integer netScore = getNetworkScore(scanResult, false); - externalScoreEvaluator.evalSavedCandidate(netScore, network, scanResult); - continue; - } - boolean sameNetwork = - mCurrentConnectedNetwork == null - ? false - : (mCurrentConnectedNetwork.networkId == network.networkId - || network.isLinked(mCurrentConnectedNetwork)); - boolean sameBssid = - mCurrentBssid == null - ? false - : mCurrentBssid.equals(scanResult.BSSID); - boolean sameSelect = - lastUserSelectedNetwork == null - ? false - : lastUserSelectedNetwork.networkId == network.networkId; - score = calculateBssidScore(scanResult, network, sameNetwork, sameBssid, sameSelect, - scoreHistory); - if (score > highestScore) { - highestScore = score; - candidateNetworkIdForThisScan = network.networkId; - potentialCandidate = network; - } - - // Update the cached candidate. - if (score > status.getCandidateScore() || (score == status.getCandidateScore() - && status.getCandidate() != null - && scanResult.level > status.getCandidate().level)) { - mWifiConfigManager.setNetworkCandidateScanResult( - candidateNetworkIdForThisScan, scanResult, score); - } - } - // Create potential filteredScanDetail entry. - filteredScanDetails.add(Pair.create(scanDetail, potentialCandidate)); - - if (highestScore > currentHighestScore || (highestScore == currentHighestScore - && scanResultCandidate != null - && scanResult.level > scanResultCandidate.level)) { - currentHighestScore = highestScore; - scanResultCandidate = scanResult; - mWifiConfigManager.setNetworkCandidateScanResult( - candidateNetworkIdForThisScan, scanResultCandidate, currentHighestScore); - // Reload the network config with the updated info. - networkCandidate = - mWifiConfigManager.getConfiguredNetwork(candidateNetworkIdForThisScan); - } - } - - mFilteredScanDetails = filteredScanDetails; - - // Kick the score manager if there is any unscored network. - if (mScoreManager != null && unscoredNetworks.size() != 0) { - NetworkKey[] unscoredNetworkKeys = - unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]); - mScoreManager.requestScores(unscoredNetworkKeys); - } - - if (mDbg) { - if (lowSignalScan.length() != 0) { - localLog(lowSignalScan + " skipped due to low signal"); - } - if (notSavedScan.length() != 0) { - localLog(notSavedScan + " skipped due to not saved"); - } - if (noValidSsid.length() != 0) { - localLog(noValidSsid + " skipped due to invalid SSID"); - } - if (unwantedBand.length() != 0) { - localLog(unwantedBand + " skipped due to user band preference"); - } - localLog(scoreHistory.toString()); - } - - // Traverse the whole user preference to choose the one user likes the most. - if (scanResultCandidate != null) { - WifiConfiguration tempConfig = networkCandidate; - while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { - String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); - tempConfig = mWifiConfigManager.getConfiguredNetwork(key); - if (tempConfig != null) { - WifiConfiguration.NetworkSelectionStatus tempStatus = - tempConfig.getNetworkSelectionStatus(); - if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) { - scanResultCandidate = tempStatus.getCandidate(); - networkCandidate = tempConfig; - } - } else { - // We should not come here in theory. - localLoge("Connect choice: " + key + " has no corresponding saved config"); - break; - } - } - localLog("After user choice adjustment, the final candidate is:" - + getNetworkString(networkCandidate) + " : " + scanResultCandidate.BSSID); - } - - // At this point none of the saved networks were good candidates so we fall back to - // externally scored networks if any are available. - if (scanResultCandidate == null) { - localLog("Checking the externalScoreEvaluator for candidates..."); - networkCandidate = - getExternalScoreCandidateNetwork(externalScoreEvaluator); - if (networkCandidate != null) { - scanResultCandidate = networkCandidate.getNetworkSelectionStatus().getCandidate(); - } - } - - if (scanResultCandidate == null) { - localLog("Can not find any suitable candidates"); - return null; - } - - String currentAssociationId = mCurrentConnectedNetwork == null ? "Disconnected" : - getNetworkString(mCurrentConnectedNetwork); - String targetAssociationId = getNetworkString(networkCandidate); - // TODO(b/31065385): In passpoint, saved configuration is initialized with a fake SSID. - // Now update it with the real SSID from the scan result. - - mCurrentBssid = scanResultCandidate.BSSID; - mCurrentConnectedNetwork = networkCandidate; - mLastQualifiedNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis(); - return networkCandidate; - } - - /** - * Returns the best candidate network according to the given ExternalScoreEvaluator. - */ - @Nullable - WifiConfiguration getExternalScoreCandidateNetwork(ExternalScoreEvaluator scoreEvaluator) { - int candidateNetworkId = WifiConfiguration.INVALID_NETWORK_ID; - switch (scoreEvaluator.getBestCandidateType()) { - case ExternalScoreEvaluator.BestCandidateType.UNTRUSTED_NETWORK: - ScanResult untrustedScanResultCandidate = - scoreEvaluator.getScanResultCandidate(); - WifiConfiguration unTrustedNetworkCandidate = - ScanResultUtil.createNetworkFromScanResult(untrustedScanResultCandidate); - // Mark this config as ephemeral so it isn't persisted. - unTrustedNetworkCandidate.ephemeral = true; - if (mNetworkScoreCache != null) { - unTrustedNetworkCandidate.meteredHint = - mNetworkScoreCache.getMeteredHint(untrustedScanResultCandidate); - } - NetworkUpdateResult result = - mWifiConfigManager.addOrUpdateNetwork( - unTrustedNetworkCandidate, Process.WIFI_UID); - if (!result.isSuccess()) { - Log.e(TAG, "Failed to add ephemeral network"); - break; - } - candidateNetworkId = result.getNetworkId(); - mWifiConfigManager.setNetworkCandidateScanResult( - candidateNetworkId, untrustedScanResultCandidate, 0); - localLog(String.format("new ephemeral candidate %s network ID:%d, " - + "meteredHint=%b", - toScanId(untrustedScanResultCandidate), candidateNetworkId, - unTrustedNetworkCandidate.meteredHint)); - break; - - case ExternalScoreEvaluator.BestCandidateType.SAVED_NETWORK: - ScanResult scanResultCandidate = scoreEvaluator.getScanResultCandidate(); - candidateNetworkId = scoreEvaluator.getSavedConfig().networkId; - mWifiConfigManager.setNetworkCandidateScanResult( - candidateNetworkId, scanResultCandidate, 0); - localLog(String.format("new scored candidate %s network ID:%d", - toScanId(scanResultCandidate), candidateNetworkId)); - break; - - case ExternalScoreEvaluator.BestCandidateType.NONE: - localLog("ExternalScoreEvaluator did not see any good candidates."); - break; - - default: - localLoge("Unhandled ExternalScoreEvaluator case. No candidate selected."); - break; - } - return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId); - } - - /** - * Returns the available external network score or NULL if no score is available. - * - * @param scanResult The scan result of the network to score. - * @param isActiveNetwork Whether or not the network is currently connected. - * @return A valid external score if one is available or NULL. - */ - @Nullable - Integer getNetworkScore(ScanResult scanResult, boolean isActiveNetwork) { - if (mNetworkScoreCache != null && mNetworkScoreCache.isScoredNetwork(scanResult)) { - int networkScore = mNetworkScoreCache.getNetworkScore(scanResult, isActiveNetwork); - localLog(toScanId(scanResult) + " has score: " + networkScore); - return networkScore; - } - return null; - } - - /** - * Formats the given ScanResult as a scan ID for logging. - */ - private static String toScanId(@Nullable ScanResult scanResult) { - return scanResult == null ? "NULL" - : String.format("%s:%s", scanResult.SSID, scanResult.BSSID); - } - - // Dump the logs. - void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("Dump of WifiQualifiedNetworkSelector"); - pw.println("WifiQualifiedNetworkSelector - Log Begin ----"); - mLocalLog.dump(fd, pw, args); - pw.println("WifiQualifiedNetworkSelector - Log End ----"); - } - - /** - * Used to track and evaluate networks that are assigned external scores. - */ - static class ExternalScoreEvaluator { - @Retention(RetentionPolicy.SOURCE) - @interface BestCandidateType { - int NONE = 0; - int SAVED_NETWORK = 1; - int UNTRUSTED_NETWORK = 2; - } - // Always set to the best known candidate. - private @BestCandidateType int mBestCandidateType = BestCandidateType.NONE; - private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; - private WifiConfiguration mSavedConfig; - private ScanResult mScanResultCandidate; - private final LocalLog mLocalLog; - private final boolean mDbg; - - ExternalScoreEvaluator(LocalLog localLog, boolean dbg) { - mLocalLog = localLog; - mDbg = dbg; - } - - // Determines whether or not the given scan result is the best one its seen so far. - void evalUntrustedCandidate(@Nullable Integer score, ScanResult scanResult) { - if (score != null && score > mHighScore) { - mHighScore = score; - mScanResultCandidate = scanResult; - mBestCandidateType = BestCandidateType.UNTRUSTED_NETWORK; - localLog(toScanId(scanResult) + " become the new untrusted candidate"); - } - } - - // Determines whether or not the given saved network is the best one its seen so far. - void evalSavedCandidate(@Nullable Integer score, WifiConfiguration config, - ScanResult scanResult) { - // Always take the highest score. If there's a tie and an untrusted network is currently - // the best then pick the saved network. - if (score != null - && (score > mHighScore - || (mBestCandidateType == BestCandidateType.UNTRUSTED_NETWORK - && score == mHighScore))) { - mHighScore = score; - mSavedConfig = config; - mScanResultCandidate = scanResult; - mBestCandidateType = BestCandidateType.SAVED_NETWORK; - localLog(toScanId(scanResult) + " become the new externally scored saved network " - + "candidate"); - } - } - - int getBestCandidateType() { - return mBestCandidateType; - } - - int getHighScore() { - return mHighScore; - } - - public ScanResult getScanResultCandidate() { - return mScanResultCandidate; - } - - WifiConfiguration getSavedConfig() { - return mSavedConfig; - } - - private void localLog(String log) { - if (mDbg) { - mLocalLog.log(log); - } - } - } -} diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index b96585ed5..69f8836fa 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -193,7 +193,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss private WifiConfigManager mWifiConfigManager; private WifiSupplicantControl mWifiSupplicantControl; private WifiConnectivityManager mWifiConnectivityManager; - private WifiQualifiedNetworkSelector mWifiQualifiedNetworkSelector; + private WifiNetworkSelector mWifiNetworkSelector; private INetworkManagementService mNwService; private IWificond mWificond; private IClientInterface mClientInterface; @@ -879,8 +879,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mWifiDiagnostics = mWifiInjector.makeWifiDiagnostics(mWifiNative); mWifiInfo = new WifiInfo(); - mWifiQualifiedNetworkSelector = new WifiQualifiedNetworkSelector(mWifiConfigManager, - mContext, mWifiInfo, mWifiInjector.getClock()); + mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, + mWifiInfo, mWifiInjector.getClock()); mSupplicantStateTracker = mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler()); @@ -1178,10 +1178,6 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss mWifiNative.enableVerboseLogging(verbose); mWifiConfigManager.enableVerboseLogging(verbose); mSupplicantStateTracker.enableVerboseLogging(verbose); - mWifiQualifiedNetworkSelector.enableVerboseLogging(verbose); - if (mWifiConnectivityManager != null) { - mWifiConnectivityManager.enableVerboseLogging(verbose); - } } private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL"; @@ -2137,11 +2133,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss pw.println(); mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION); mWifiDiagnostics.dump(fd, pw, args); - mWifiQualifiedNetworkSelector.dump(fd, pw, args); dumpIpManager(fd, pw, args); - if (mWifiConnectivityManager != null) { - mWifiConnectivityManager.dump(fd, pw, args); - } + mWifiNetworkSelector.dump(fd, pw, args); } public void handleUserSwitch(int userId) { @@ -4169,7 +4162,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss synchronized (mWifiReqCountLock) { mWifiConnectivityManager = new WifiConnectivityManager(mContext, WifiStateMachine.this, mWifiScanner, mWifiConfigManager, mWifiInfo, - mWifiQualifiedNetworkSelector, mWifiInjector, + mWifiNetworkSelector, mWifiInjector, getHandler().getLooper(), hasConnectionRequests()); mWifiConnectivityManager.setUntrustedConnectionAllowed(mUntrustedReqCount > 0); mWifiConnectivityManager.handleScreenStateChanged(mScreenOn); @@ -6177,7 +6170,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss || (lastRoam > 0 && lastRoam < 2000) /* unless driver is roaming */) && ((ScanResult.is24GHz(mWifiInfo.getFrequency()) && mWifiInfo.getRssi() > - WifiQualifiedNetworkSelector.QUALIFIED_RSSI_24G_BAND) + mThresholdQualifiedRssi5) || (ScanResult.is5GHz(mWifiInfo.getFrequency()) && mWifiInfo.getRssi() > mThresholdQualifiedRssi5))) { |