blob: 0e122895c0f4de95779c7d88f529694f9c5be466 [file] [log] [blame]
/*
* 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.net.wifi.WifiNetworkScoreCache;
import android.os.Process;
import android.text.TextUtils;
import android.util.LocalLog;
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.
* @deprecated TODO(b/33694078): remove this evaluator in favor of RecommendedNetworkEvaluator
*/
@Deprecated
public class ExternalScoreEvaluator implements WifiNetworkSelector.NetworkEvaluator {
private static final String NAME = "WifiExternalScoreEvaluator";
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,
WifiNetworkScoreCache scoreCache, Clock clock, LocalLog localLog) {
mWifiConfigManager = configManager;
mClock = clock;
mLocalLog = localLog;
mScoreCache = scoreCache;
mScoreManager =
(NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
if (mScoreManager != null) {
mScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache);
} else {
localLog("Couldn't get NETWORK_SCORE_SERVICE.");
}
}
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) {
localLog("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.
* @param scoreCache Wifi network score cache.
* @param active Flag which indicates whether this is the currently connected network.
* @return A valid external score if one is available or NULL.
*/
@Nullable
Integer getNetworkScore(ScanResult scanResult, WifiNetworkScoreCache scoreCache,
boolean active) {
if (scoreCache != null && scoreCache.isScoredNetwork(scanResult)) {
int score = scoreCache.getNetworkScore(scanResult, active);
localLog(WifiNetworkSelector.toScanId(scanResult) + " has score: " + score
+ " active network: " + active);
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()) {
localLog("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(
ScanResultUtil.createQuotedSSID(scanResult.SSID))) {
// Ephemeral network has either one WifiConfiguration or none yet.
// Checking BSSID is sufficient to determine whether this is the
// currently connected network.
boolean active = TextUtils.equals(currentBssid, scanResult.BSSID);
Integer score = getNetworkScore(scanResult, mScoreCache, active);
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");
boolean active = currentNetwork != null && currentNetwork == network
&& TextUtils.equals(currentBssid, scanResult.BSSID);
Integer score = getNetworkScore(scanResult, mScoreCache, active);
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);
}
}
}
}