1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.net.NetworkKey; 23 import android.net.NetworkScoreManager; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.WifiConfiguration; 26 import android.net.wifi.WifiNetworkScoreCache; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Process; 30 import android.provider.Settings; 31 import android.text.TextUtils; 32 import android.util.LocalLog; 33 import android.util.Log; 34 import android.util.Pair; 35 36 import com.android.server.wifi.util.ScanResultUtil; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * {@link WifiNetworkSelector.NetworkEvaluator} implementation that uses scores obtained by 43 * {@link NetworkScoreManager#requestScores(NetworkKey[])} to make network connection decisions. 44 */ 45 public class ScoredNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator { 46 private static final String TAG = "ScoredNetworkEvaluator"; 47 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 48 49 private final NetworkScoreManager mNetworkScoreManager; 50 private final WifiConfigManager mWifiConfigManager; 51 private final LocalLog mLocalLog; 52 private final ContentObserver mContentObserver; 53 private boolean mNetworkRecommendationsEnabled; 54 private WifiNetworkScoreCache mScoreCache; 55 56 ScoredNetworkEvaluator(final Context context, Looper looper, 57 final FrameworkFacade frameworkFacade, NetworkScoreManager networkScoreManager, 58 WifiConfigManager wifiConfigManager, LocalLog localLog, 59 WifiNetworkScoreCache wifiNetworkScoreCache) { 60 mScoreCache = wifiNetworkScoreCache; 61 mNetworkScoreManager = networkScoreManager; 62 mWifiConfigManager = wifiConfigManager; 63 mLocalLog = localLog; 64 mContentObserver = new ContentObserver(new Handler(looper)) { 65 @Override 66 public void onChange(boolean selfChange) { 67 mNetworkRecommendationsEnabled = frameworkFacade.getIntegerSetting(context, 68 Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1; 69 } 70 }; 71 frameworkFacade.registerContentObserver(context, 72 Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), 73 false /* notifyForDescendents */, mContentObserver); 74 mContentObserver.onChange(false /* unused */); 75 mLocalLog.log("ScoredNetworkEvaluator constructed. mNetworkRecommendationsEnabled: " 76 + mNetworkRecommendationsEnabled); 77 } 78 79 @Override 80 public void update(List<ScanDetail> scanDetails) { 81 if (mNetworkRecommendationsEnabled) { 82 updateNetworkScoreCache(scanDetails); 83 } 84 } 85 86 private void updateNetworkScoreCache(List<ScanDetail> scanDetails) { 87 ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>(); 88 for (int i = 0; i < scanDetails.size(); i++) { 89 ScanResult scanResult = scanDetails.get(i).getScanResult(); 90 NetworkKey networkKey = NetworkKey.createFromScanResult(scanResult); 91 if (networkKey != null) { 92 // Is there a ScoredNetwork for this ScanResult? If not, request a score. 93 if (mScoreCache.getScoredNetwork(networkKey) == null) { 94 unscoredNetworks.add(networkKey); 95 } 96 } 97 } 98 99 // Kick the score manager if there are any unscored network. 100 if (!unscoredNetworks.isEmpty()) { 101 NetworkKey[] unscoredNetworkKeys = 102 unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]); 103 mNetworkScoreManager.requestScores(unscoredNetworkKeys); 104 } 105 } 106 107 @Override 108 public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails, 109 WifiConfiguration currentNetwork, String currentBssid, boolean connected, 110 boolean untrustedNetworkAllowed, 111 List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) { 112 if (!mNetworkRecommendationsEnabled) { 113 mLocalLog.log("Skipping evaluateNetworks; Network recommendations disabled."); 114 return null; 115 } 116 117 final ScoreTracker scoreTracker = new ScoreTracker(); 118 for (int i = 0; i < scanDetails.size(); i++) { 119 ScanDetail scanDetail = scanDetails.get(i); 120 ScanResult scanResult = scanDetail.getScanResult(); 121 if (scanResult == null) continue; 122 if (mWifiConfigManager.wasEphemeralNetworkDeleted( 123 ScanResultUtil.createQuotedSSID(scanResult.SSID))) { 124 debugLog("Ignoring disabled ephemeral SSID: " + scanResult.SSID); 125 continue; 126 } 127 final WifiConfiguration configuredNetwork = 128 mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail); 129 boolean untrustedScanResult = configuredNetwork == null || configuredNetwork.ephemeral; 130 131 if (!untrustedNetworkAllowed && untrustedScanResult) { 132 continue; 133 } 134 135 // Track scan results for open wifi networks 136 if (configuredNetwork == null) { 137 if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { 138 scoreTracker.trackUntrustedCandidate(scanResult); 139 } 140 continue; 141 } 142 143 // Ignore non-ephemeral and non-externally scored networks 144 if (!configuredNetwork.ephemeral && !configuredNetwork.useExternalScores) { 145 continue; 146 } 147 148 // Ignore externally scored or ephemeral networks that have been disabled for selection 149 if (!configuredNetwork.getNetworkSelectionStatus().isNetworkEnabled()) { 150 debugLog("Ignoring disabled SSID: " + configuredNetwork.SSID); 151 continue; 152 } 153 154 // TODO(b/37485956): consider applying a boost for networks with only the same SSID 155 boolean isCurrentNetwork = currentNetwork != null 156 && currentNetwork.networkId == configuredNetwork.networkId 157 && TextUtils.equals(currentBssid, scanResult.BSSID); 158 if (configuredNetwork.ephemeral) { 159 scoreTracker.trackUntrustedCandidate( 160 scanResult, configuredNetwork, isCurrentNetwork); 161 } else { 162 scoreTracker.trackExternallyScoredCandidate( 163 scanResult, configuredNetwork, isCurrentNetwork); 164 } 165 if (connectableNetworks != null) { 166 connectableNetworks.add(Pair.create(scanDetail, configuredNetwork)); 167 } 168 } 169 170 return scoreTracker.getCandidateConfiguration(); 171 } 172 173 /** Used to track the network with the highest score. */ 174 class ScoreTracker { 175 private static final int EXTERNAL_SCORED_NONE = 0; 176 private static final int EXTERNAL_SCORED_SAVED_NETWORK = 1; 177 private static final int EXTERNAL_SCORED_UNTRUSTED_NETWORK = 2; 178 179 private int mBestCandidateType = EXTERNAL_SCORED_NONE; 180 private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE; 181 private WifiConfiguration mEphemeralConfig; 182 private WifiConfiguration mSavedConfig; 183 private ScanResult mScanResultCandidate; 184 185 /** 186 * Returns the available external network score or null if no score is available. 187 * 188 * @param scanResult The scan result of the network to score. 189 * @param isCurrentNetwork Flag which indicates whether this is the current network. 190 * @return A valid external score if one is available or NULL. 191 */ 192 @Nullable 193 private Integer getNetworkScore(ScanResult scanResult, boolean isCurrentNetwork) { 194 if (mScoreCache.isScoredNetwork(scanResult)) { 195 int score = mScoreCache.getNetworkScore(scanResult, isCurrentNetwork); 196 if (DEBUG) { 197 mLocalLog.log(WifiNetworkSelector.toScanId(scanResult) + " has score: " 198 + score + " isCurrentNetwork network: " + isCurrentNetwork); 199 } 200 return score; 201 } 202 return null; 203 } 204 205 /** Track an untrusted {@link ScanResult}. */ 206 void trackUntrustedCandidate(ScanResult scanResult) { 207 Integer score = getNetworkScore(scanResult, false /* isCurrentNetwork */); 208 if (score != null && score > mHighScore) { 209 mHighScore = score; 210 mScanResultCandidate = scanResult; 211 mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK; 212 debugLog(WifiNetworkSelector.toScanId(scanResult) 213 + " becomes the new untrusted candidate."); 214 } 215 } 216 217 /** 218 * Track an untrusted {@link ScanResult} that already has a corresponding 219 * ephemeral {@link WifiConfiguration}. 220 */ 221 void trackUntrustedCandidate( 222 ScanResult scanResult, WifiConfiguration config, boolean isCurrentNetwork) { 223 Integer score = getNetworkScore(scanResult, isCurrentNetwork); 224 if (score != null && score > mHighScore) { 225 mHighScore = score; 226 mScanResultCandidate = scanResult; 227 mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK; 228 mEphemeralConfig = config; 229 mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0); 230 debugLog(WifiNetworkSelector.toScanId(scanResult) 231 + " becomes the new untrusted candidate."); 232 } 233 } 234 235 /** Tracks a saved network that has been marked with useExternalScores */ 236 void trackExternallyScoredCandidate( 237 ScanResult scanResult, WifiConfiguration config, boolean isCurrentNetwork) { 238 // Always take the highest score. If there's a tie and an untrusted network is currently 239 // the best then pick the saved network. 240 Integer score = getNetworkScore(scanResult, isCurrentNetwork); 241 if (score != null 242 && (score > mHighScore 243 || (mBestCandidateType == EXTERNAL_SCORED_UNTRUSTED_NETWORK 244 && score == mHighScore))) { 245 mHighScore = score; 246 mSavedConfig = config; 247 mScanResultCandidate = scanResult; 248 mBestCandidateType = EXTERNAL_SCORED_SAVED_NETWORK; 249 mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0); 250 debugLog(WifiNetworkSelector.toScanId(scanResult) 251 + " becomes the new externally scored saved network candidate."); 252 } 253 } 254 255 /** Returns the best candidate network tracked by this {@link ScoreTracker}. */ 256 @Nullable 257 WifiConfiguration getCandidateConfiguration() { 258 int candidateNetworkId = WifiConfiguration.INVALID_NETWORK_ID; 259 switch (mBestCandidateType) { 260 case ScoreTracker.EXTERNAL_SCORED_UNTRUSTED_NETWORK: 261 if (mEphemeralConfig != null) { 262 candidateNetworkId = mEphemeralConfig.networkId; 263 mLocalLog.log(String.format("existing ephemeral candidate %s network ID:%d" 264 + ", meteredHint=%b", 265 WifiNetworkSelector.toScanId(mScanResultCandidate), 266 candidateNetworkId, 267 mEphemeralConfig.meteredHint)); 268 break; 269 } 270 271 mEphemeralConfig = 272 ScanResultUtil.createNetworkFromScanResult(mScanResultCandidate); 273 // Mark this config as ephemeral so it isn't persisted. 274 mEphemeralConfig.ephemeral = true; 275 mEphemeralConfig.meteredHint = mScoreCache.getMeteredHint(mScanResultCandidate); 276 NetworkUpdateResult result = 277 mWifiConfigManager.addOrUpdateNetwork(mEphemeralConfig, 278 Process.WIFI_UID); 279 if (!result.isSuccess()) { 280 mLocalLog.log("Failed to add ephemeral network"); 281 break; 282 } 283 if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(), 284 WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) { 285 mLocalLog.log("Failed to make ephemeral network selectable"); 286 break; 287 } 288 candidateNetworkId = result.getNetworkId(); 289 mWifiConfigManager.setNetworkCandidateScanResult(candidateNetworkId, 290 mScanResultCandidate, 0); 291 mLocalLog.log(String.format("new ephemeral candidate %s network ID:%d, " 292 + "meteredHint=%b", 293 WifiNetworkSelector.toScanId(mScanResultCandidate), 294 candidateNetworkId, 295 mEphemeralConfig.meteredHint)); 296 break; 297 case ScoreTracker.EXTERNAL_SCORED_SAVED_NETWORK: 298 candidateNetworkId = mSavedConfig.networkId; 299 mLocalLog.log(String.format("new saved network candidate %s network ID:%d", 300 WifiNetworkSelector.toScanId(mScanResultCandidate), 301 candidateNetworkId)); 302 break; 303 case ScoreTracker.EXTERNAL_SCORED_NONE: 304 default: 305 mLocalLog.log("ScoredNetworkEvaluator did not see any good candidates."); 306 break; 307 } 308 return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId); 309 } 310 } 311 312 private void debugLog(String msg) { 313 if (DEBUG) { 314 mLocalLog.log(msg); 315 } 316 } 317 318 @Override 319 public String getName() { 320 return TAG; 321 } 322 } 323