1 /* 2 * Copyright (C) 2016 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 static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.app.admin.DevicePolicyManager; 26 import android.app.admin.WifiSsidPolicy; 27 import android.content.Context; 28 import android.net.MacAddress; 29 import android.net.wifi.ScanResult; 30 import android.net.wifi.SecurityParams; 31 import android.net.wifi.SupplicantState; 32 import android.net.wifi.WifiConfiguration; 33 import android.net.wifi.WifiInfo; 34 import android.net.wifi.WifiSsid; 35 import android.net.wifi.util.ScanResultUtil; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 import android.util.ArrayMap; 39 import android.util.ArraySet; 40 import android.util.LocalLog; 41 import android.util.Log; 42 import android.util.Pair; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.util.Preconditions; 46 import com.android.modules.utils.build.SdkLevel; 47 import com.android.server.wifi.hotspot2.NetworkDetail; 48 import com.android.server.wifi.proto.nano.WifiMetricsProto; 49 import com.android.server.wifi.util.InformationElementUtil.BssLoad; 50 import com.android.server.wifi.util.WifiPermissionsUtil; 51 import com.android.wifi.resources.R; 52 53 import java.lang.annotation.Retention; 54 import java.lang.annotation.RetentionPolicy; 55 import java.util.ArrayList; 56 import java.util.Collection; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.Set; 62 import java.util.concurrent.TimeUnit; 63 import java.util.stream.Collectors; 64 65 /** 66 * WifiNetworkSelector looks at all the connectivity scan results and 67 * runs all the nominators to find or create matching configurations. 68 * Then it makes a final selection from among the resulting candidates. 69 */ 70 public class WifiNetworkSelector { 71 private static final String TAG = "WifiNetworkSelector"; 72 73 private static final long INVALID_TIME_STAMP = Long.MIN_VALUE; 74 75 /** 76 * Minimum time gap between last successful network selection and a 77 * new selection attempt. 78 */ 79 @VisibleForTesting 80 public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000; 81 82 /** 83 * Connected score value used to decide whether a still-connected wifi should be treated 84 * as unconnected when filtering scan results. 85 */ 86 @VisibleForTesting 87 public static final int WIFI_POOR_SCORE = ConnectedScore.WIFI_TRANSITION_SCORE - 10; 88 89 /** 90 * The identifier string of the CandidateScorer to use (in the absence of overrides). 91 */ 92 public static final String PRESET_CANDIDATE_SCORER_NAME = "ThroughputScorer"; 93 94 /** 95 * Experiment ID for the legacy scorer. 96 */ 97 public static final int LEGACY_CANDIDATE_SCORER_EXP_ID = 0; 98 99 private final Context mContext; 100 private final WifiConfigManager mWifiConfigManager; 101 private final Clock mClock; 102 private final LocalLog mLocalLog; 103 private boolean mVerboseLoggingEnabled = false; 104 private final WifiMetrics mWifiMetrics; 105 private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP; 106 // Buffer of filtered scan results (Scan results considered by network selection) & associated 107 // WifiConfiguration (if any). 108 private final List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks = 109 new ArrayList<>(); 110 private List<ScanDetail> mFilteredNetworks = new ArrayList<>(); 111 private final WifiScoreCard mWifiScoreCard; 112 private final ScoringParams mScoringParams; 113 private final WifiInjector mWifiInjector; 114 private final ThroughputPredictor mThroughputPredictor; 115 private final WifiChannelUtilization mWifiChannelUtilization; 116 private final WifiGlobals mWifiGlobals; 117 private final ScanRequestProxy mScanRequestProxy; 118 119 private final Map<String, WifiCandidates.CandidateScorer> mCandidateScorers = new ArrayMap<>(); 120 private boolean mIsEnhancedOpenSupportedInitialized = false; 121 private boolean mIsEnhancedOpenSupported; 122 123 /** 124 * Interface for WiFi Network Nominator 125 * 126 * A network nominator examines the scan results reports the 127 * connectable candidates in its category for further consideration. 128 */ 129 public interface NetworkNominator { 130 /** Type of nominators */ 131 int NOMINATOR_ID_SAVED = 0; 132 int NOMINATOR_ID_SUGGESTION = 1; 133 int NOMINATOR_ID_SCORED = 4; 134 int NOMINATOR_ID_CURRENT = 5; // Should always be last 135 136 @IntDef(prefix = {"NOMINATOR_ID_"}, value = { 137 NOMINATOR_ID_SAVED, 138 NOMINATOR_ID_SUGGESTION, 139 NOMINATOR_ID_SCORED, 140 NOMINATOR_ID_CURRENT}) 141 @Retention(RetentionPolicy.SOURCE) 142 public @interface NominatorId { 143 } 144 145 /** 146 * Get the nominator type. 147 */ 148 @NominatorId getId()149 int getId(); 150 151 /** 152 * Get the nominator name. 153 */ getName()154 String getName(); 155 156 /** 157 * Update the nominator. 158 * 159 * Certain nominators have to be updated with the new scan results. For example 160 * the ScoredNetworkNominator needs to refresh its Score Cache. 161 * 162 * @param scanDetails a list of scan details constructed from the scan results 163 */ update(List<ScanDetail> scanDetails)164 void update(List<ScanDetail> scanDetails); 165 166 /** 167 * Evaluate all the networks from the scan results. 168 * @param scanDetails a list of scan details constructed from the scan results 169 * @param untrustedNetworkAllowed a flag to indicate if untrusted networks are allowed 170 * @param oemPaidNetworkAllowed a flag to indicate if oem paid networks are allowed 171 * @param oemPrivateNetworkAllowed a flag to indicate if oem private networks are allowed 172 * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network 173 * @param onConnectableListener callback to record all of the connectable networks 174 */ nominateNetworks(List<ScanDetail> scanDetails, boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, Set<Integer> restrictedNetworkAllowedUids, OnConnectableListener onConnectableListener)175 void nominateNetworks(List<ScanDetail> scanDetails, 176 boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, 177 boolean oemPrivateNetworkAllowed, Set<Integer> restrictedNetworkAllowedUids, 178 OnConnectableListener onConnectableListener); 179 180 /** 181 * Callback for recording connectable candidates 182 */ 183 public interface OnConnectableListener { 184 /** 185 * Notes that an access point is an eligible connection candidate 186 * 187 * @param scanDetail describes the specific access point 188 * @param config is the WifiConfiguration for the network 189 */ onConnectable(ScanDetail scanDetail, WifiConfiguration config)190 void onConnectable(ScanDetail scanDetail, WifiConfiguration config); 191 } 192 } 193 194 private final List<NetworkNominator> mNominators = new ArrayList<>(3); 195 196 // A helper to log debugging information in the local log buffer, which can 197 // be retrieved in bugreport. It is also used to print the log in the console. localLog(String log)198 private void localLog(String log) { 199 mLocalLog.log(log); 200 if (mVerboseLoggingEnabled) Log.d(TAG, log); 201 } 202 203 /** 204 * Enable verbose logging in the console 205 */ enableVerboseLogging(boolean verbose)206 public void enableVerboseLogging(boolean verbose) { 207 mVerboseLoggingEnabled = verbose; 208 } 209 210 /** 211 * Check if current network has sufficient RSSI 212 * 213 * @param wifiInfo info of currently connected network 214 * @return true if current link quality is sufficient, false otherwise. 215 */ hasSufficientLinkQuality(WifiInfo wifiInfo)216 public boolean hasSufficientLinkQuality(WifiInfo wifiInfo) { 217 int currentRssi = wifiInfo.getRssi(); 218 return currentRssi >= mScoringParams.getSufficientRssi(wifiInfo.getFrequency()); 219 } 220 221 /** 222 * Check if current network has active Tx or Rx traffic 223 * 224 * @param wifiInfo info of currently connected network 225 * @return true if it has active Tx or Rx traffic, false otherwise. 226 */ hasActiveStream(WifiInfo wifiInfo)227 public boolean hasActiveStream(WifiInfo wifiInfo) { 228 return wifiInfo.getSuccessfulTxPacketsPerSecond() 229 > mScoringParams.getActiveTrafficPacketsPerSecond() 230 || wifiInfo.getSuccessfulRxPacketsPerSecond() 231 > mScoringParams.getActiveTrafficPacketsPerSecond(); 232 } 233 234 /** 235 * Check if current network has internet or is expected to not have internet 236 */ hasInternetOrExpectNoInternet(WifiInfo wifiInfo)237 public boolean hasInternetOrExpectNoInternet(WifiInfo wifiInfo) { 238 WifiConfiguration network = 239 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); 240 if (network == null) { 241 return false; 242 } 243 return !network.hasNoInternetAccess() || network.isNoInternetAccessExpected(); 244 } 245 /** 246 * Determines whether the currently connected network is sufficient. 247 * 248 * If the network is good enough, or if switching to a new network is likely to 249 * be disruptive, we should avoid doing a network selection. 250 * 251 * @param wifiInfo info of currently connected network 252 * @return true if the network is sufficient 253 */ isNetworkSufficient(WifiInfo wifiInfo)254 public boolean isNetworkSufficient(WifiInfo wifiInfo) { 255 // Currently connected? 256 if (wifiInfo.getSupplicantState() != SupplicantState.COMPLETED) { 257 return false; 258 } 259 260 localLog("Current connected network: " + wifiInfo.getNetworkId()); 261 262 WifiConfiguration network = 263 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); 264 265 if (network == null) { 266 localLog("Current network was removed"); 267 return false; 268 } 269 270 // Skip autojoin for the first few seconds of a user-initiated connection. 271 // This delays network selection during the time that connectivity service may be posting 272 // a dialog about a no-internet network. 273 if (mWifiConfigManager.getLastSelectedNetwork() == network.networkId 274 && (mClock.getElapsedSinceBootMillis() 275 - mWifiConfigManager.getLastSelectedTimeStamp()) 276 <= mContext.getResources().getInteger( 277 R.integer.config_wifiSufficientDurationAfterUserSelectionMilliseconds)) { 278 localLog("Current network is recently user-selected"); 279 return true; 280 } 281 282 // Set OSU (Online Sign Up) network for Passpoint Release 2 to sufficient 283 // so that network select selection is skipped and OSU process can complete. 284 if (network.osu) { 285 localLog("Current connection is OSU"); 286 return true; 287 } 288 289 // Current network is set as unusable by the external scorer. 290 if (!wifiInfo.isUsable()) { 291 return false; 292 } 293 294 // External scorer is not being used, and the current network's score is below the 295 // sufficient score threshold configured for the AOSP scorer. 296 if (!mWifiGlobals.isUsingExternalScorer() 297 && wifiInfo.getScore() 298 < mWifiGlobals.getWifiLowConnectedScoreThresholdToTriggerScanForMbb()) { 299 return false; 300 } 301 302 // OEM paid/private networks are only available to system apps, so this is never sufficient. 303 if (network.oemPaid || network.oemPrivate) { 304 localLog("Current network is oem paid/private"); 305 return false; 306 } 307 308 // Metered networks costs the user data, so this is insufficient. 309 if (network.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) { 310 localLog("Current network is metered"); 311 return false; 312 } 313 314 // Network without internet access is not sufficient, unless expected 315 if (!hasInternetOrExpectNoInternet(wifiInfo)) { 316 localLog("Current network has [" + network.numNoInternetAccessReports 317 + "] no-internet access reports"); 318 return false; 319 } 320 321 if (!hasSufficientLinkQuality(wifiInfo) && !hasActiveStream(wifiInfo)) { 322 localLog("Current network link quality is not sufficient and has low ongoing traffic"); 323 return false; 324 } 325 326 return true; 327 } 328 isNetworkSelectionNeededForCmm(@onNull ClientModeManagerState cmmState)329 private boolean isNetworkSelectionNeededForCmm(@NonNull ClientModeManagerState cmmState) { 330 if (cmmState.connected) { 331 // Is roaming allowed? 332 if (!mContext.getResources().getBoolean( 333 R.bool.config_wifi_framework_enable_associated_network_selection)) { 334 localLog(cmmState.ifaceName + ": Switching networks in connected state is not " 335 + "allowed. Skip network selection."); 336 return false; 337 } 338 339 // Has it been at least the minimum interval since last network selection? 340 if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) { 341 long gap = mClock.getElapsedSinceBootMillis() 342 - mLastNetworkSelectionTimeStamp; 343 if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) { 344 localLog(cmmState.ifaceName + ": Too short since last network selection: " 345 + gap + " ms. Skip network selection."); 346 return false; 347 } 348 } 349 // Please note other scans (e.g., location scan or app scan) may also trigger network 350 // selection and these scans may or may not run sufficiency check. 351 // So it is better to run sufficiency check here before network selection. 352 if (isNetworkSufficient(cmmState.wifiInfo)) { 353 localLog(cmmState.ifaceName 354 + ": Current connected network already sufficient." 355 + " Skip network selection."); 356 return false; 357 } else { 358 localLog(cmmState.ifaceName + ": Current connected network is not sufficient."); 359 return true; 360 } 361 } else if (cmmState.disconnected) { 362 return true; 363 } else { 364 // No network selection if ClientModeImpl is in a state other than 365 // connected or disconnected (i.e connecting). 366 localLog(cmmState.ifaceName + ": ClientModeImpl is in neither CONNECTED nor " 367 + "DISCONNECTED state. Skip network selection."); 368 return false; 369 } 370 371 } 372 isNetworkSelectionNeeded(@onNull List<ScanDetail> scanDetails, @NonNull List<ClientModeManagerState> cmmStates)373 private boolean isNetworkSelectionNeeded(@NonNull List<ScanDetail> scanDetails, 374 @NonNull List<ClientModeManagerState> cmmStates) { 375 if (scanDetails.size() == 0) { 376 localLog("Empty connectivity scan results. Skip network selection."); 377 return false; 378 } 379 for (ClientModeManagerState cmmState : cmmStates) { 380 // network selection needed by this CMM instance, perform network selection 381 if (isNetworkSelectionNeededForCmm(cmmState)) { 382 return true; 383 } 384 } 385 // none of the CMM instances need network selection, skip network selection. 386 return false; 387 } 388 389 /** 390 * Format the given ScanResult as a scan ID for logging. 391 */ toScanId(@ullable ScanResult scanResult)392 public static String toScanId(@Nullable ScanResult scanResult) { 393 return scanResult == null ? "NULL" 394 : scanResult.SSID + ":" + scanResult.BSSID; 395 } 396 397 /** 398 * Format the given WifiConfiguration as a SSID:netId string 399 */ toNetworkString(WifiConfiguration network)400 public static String toNetworkString(WifiConfiguration network) { 401 if (network == null) { 402 return null; 403 } 404 405 return (network.SSID + ":" + network.networkId); 406 } 407 408 /** 409 * Compares ScanResult level against the minimum threshold for its band, returns true if lower 410 */ isSignalTooWeak(ScanResult scanResult)411 public boolean isSignalTooWeak(ScanResult scanResult) { 412 return (scanResult.level < mScoringParams.getEntryRssi(scanResult.frequency)); 413 } 414 415 @SuppressLint("NewApi") filterScanResults(List<ScanDetail> scanDetails, Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates)416 private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails, 417 Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates) { 418 List<ScanDetail> validScanDetails = new ArrayList<>(); 419 StringBuffer noValidSsid = new StringBuffer(); 420 StringBuffer blockedBssid = new StringBuffer(); 421 StringBuffer lowRssi = new StringBuffer(); 422 StringBuffer mboAssociationDisallowedBssid = new StringBuffer(); 423 StringBuffer adminRestrictedSsid = new StringBuffer(); 424 List<String> currentBssids = cmmStates.stream() 425 .map(cmmState -> cmmState.wifiInfo.getBSSID()) 426 .collect(Collectors.toList()); 427 Set<String> scanResultPresentForCurrentBssids = new ArraySet<>(); 428 429 int adminMinimumSecurityLevel = 0; 430 boolean adminSsidRestrictionSet = false; 431 Set<WifiSsid> adminSsidAllowlist = new ArraySet<>(); 432 Set<WifiSsid> admindSsidDenylist = new ArraySet<>(); 433 434 int numBssidFiltered = 0; 435 436 DevicePolicyManager devicePolicyManager = 437 WifiPermissionsUtil.retrieveDevicePolicyManagerFromContext(mContext); 438 439 if (devicePolicyManager != null && SdkLevel.isAtLeastT()) { 440 adminMinimumSecurityLevel = 441 devicePolicyManager.getMinimumRequiredWifiSecurityLevel(); 442 WifiSsidPolicy policy = devicePolicyManager.getWifiSsidPolicy(); 443 if (policy != null) { 444 adminSsidRestrictionSet = true; 445 if (policy.getPolicyType() == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) { 446 adminSsidAllowlist = policy.getSsids(); 447 } else { 448 admindSsidDenylist = policy.getSsids(); 449 } 450 } 451 } 452 453 for (ScanDetail scanDetail : scanDetails) { 454 ScanResult scanResult = scanDetail.getScanResult(); 455 456 if (TextUtils.isEmpty(scanResult.SSID)) { 457 noValidSsid.append(scanResult.BSSID).append(" / "); 458 continue; 459 } 460 461 // Check if the scan results contain the currently connected BSSID's 462 if (currentBssids.contains(scanResult.BSSID)) { 463 scanResultPresentForCurrentBssids.add(scanResult.BSSID); 464 validScanDetails.add(scanDetail); 465 continue; 466 } 467 468 final String scanId = toScanId(scanResult); 469 470 if (bssidBlocklist.contains(scanResult.BSSID)) { 471 blockedBssid.append(scanId).append(" / "); 472 numBssidFiltered++; 473 continue; 474 } 475 476 // Skip network with too weak signals. 477 if (isSignalTooWeak(scanResult)) { 478 lowRssi.append(scanId); 479 if (scanResult.is24GHz()) { 480 lowRssi.append("(2.4GHz)"); 481 } else if (scanResult.is5GHz()) { 482 lowRssi.append("(5GHz)"); 483 } else if (scanResult.is6GHz()) { 484 lowRssi.append("(6GHz)"); 485 } 486 lowRssi.append(scanResult.level).append(" / "); 487 continue; 488 } 489 490 // Skip BSS which is not accepting new connections. 491 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 492 if (networkDetail != null) { 493 if (networkDetail.getMboAssociationDisallowedReasonCode() 494 != MboOceConstants.MBO_OCE_ATTRIBUTE_NOT_PRESENT) { 495 mWifiMetrics 496 .incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd(); 497 mboAssociationDisallowedBssid.append(scanId).append("(") 498 .append(networkDetail.getMboAssociationDisallowedReasonCode()) 499 .append(")").append(" / "); 500 continue; 501 } 502 } 503 504 // Skip network that does not meet the admin set SSID restriction 505 if (adminSsidRestrictionSet) { 506 WifiSsid ssid = scanResult.getWifiSsid(); 507 // Allowlist policy set but network is not present in the list 508 if (!adminSsidAllowlist.isEmpty() && !adminSsidAllowlist.contains(ssid)) { 509 adminRestrictedSsid.append(scanId).append(" / "); 510 continue; 511 } 512 // Denylist policy set but network is present in the list 513 if (!admindSsidDenylist.isEmpty() && admindSsidDenylist.contains(ssid)) { 514 adminRestrictedSsid.append(scanId).append(" / "); 515 continue; 516 } 517 } 518 519 // Skip network that does not meet the admin set minimum security level restriction 520 if (adminMinimumSecurityLevel != 0) { 521 boolean securityRestrictionPassed = false; 522 @WifiInfo.SecurityType int[] securityTypes = scanResult.getSecurityTypes(); 523 for (int type : securityTypes) { 524 int securityLevel = WifiInfo.convertSecurityTypeToDpmWifiSecurity(type); 525 526 // Skip unknown security type since security level cannot be determined. 527 // If all the security types are unknown when the minimum security level 528 // restriction is set, the scan result is ignored. 529 if (securityLevel == WifiInfo.DPM_SECURITY_TYPE_UNKNOWN) continue; 530 531 if (adminMinimumSecurityLevel <= securityLevel) { 532 securityRestrictionPassed = true; 533 break; 534 } 535 } 536 if (!securityRestrictionPassed) { 537 adminRestrictedSsid.append(scanId).append(" / "); 538 continue; 539 } 540 } 541 542 validScanDetails.add(scanDetail); 543 } 544 mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(numBssidFiltered); 545 546 // WNS listens to all single scan results. Some scan requests may not include 547 // the channel of the currently connected network, so the currently connected 548 // network won't show up in the scan results. We don't act on these scan results 549 // to avoid aggressive network switching which might trigger disconnection. 550 // TODO(b/147751334) this may no longer be needed 551 for (ClientModeManagerState cmmState : cmmStates) { 552 // TODO (b/169413079): Disable network selection on corresponding CMM instead. 553 if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE 554 && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) { 555 localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID() 556 + " is not in the scan results. Skip network selection."); 557 validScanDetails.clear(); 558 return validScanDetails; 559 } 560 } 561 562 if (noValidSsid.length() != 0) { 563 localLog("Networks filtered out due to invalid SSID: " + noValidSsid); 564 } 565 566 if (blockedBssid.length() != 0) { 567 localLog("Networks filtered out due to blocklist: " + blockedBssid); 568 } 569 570 if (lowRssi.length() != 0) { 571 localLog("Networks filtered out due to low signal strength: " + lowRssi); 572 } 573 574 if (mboAssociationDisallowedBssid.length() != 0) { 575 localLog("Networks filtered out due to mbo association disallowed indication: " 576 + mboAssociationDisallowedBssid); 577 } 578 579 if (adminRestrictedSsid.length() != 0) { 580 localLog("Networks filtered out due to admin restrictions: " + adminRestrictedSsid); 581 } 582 583 return validScanDetails; 584 } 585 findScanDetailForBssid(List<ScanDetail> scanDetails, String currentBssid)586 private ScanDetail findScanDetailForBssid(List<ScanDetail> scanDetails, 587 String currentBssid) { 588 for (ScanDetail scanDetail : scanDetails) { 589 ScanResult scanResult = scanDetail.getScanResult(); 590 if (scanResult.BSSID.equals(currentBssid)) { 591 return scanDetail; 592 } 593 } 594 return null; 595 } 596 isEnhancedOpenSupported()597 private boolean isEnhancedOpenSupported() { 598 if (mIsEnhancedOpenSupportedInitialized) { 599 return mIsEnhancedOpenSupported; 600 } 601 602 mIsEnhancedOpenSupportedInitialized = true; 603 ClientModeManager primaryManager = 604 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager(); 605 mIsEnhancedOpenSupported = (primaryManager.getSupportedFeatures() & WIFI_FEATURE_OWE) != 0; 606 return mIsEnhancedOpenSupported; 607 } 608 609 /** 610 * This returns a list of ScanDetails that were filtered in the process of network selection. 611 * The list is further filtered for only open unsaved networks. 612 * 613 * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS, 614 * blocked BSSIDS, or low signal strength. This will return an empty list when there are 615 * no open unsaved networks, or when network selection has not been run. 616 */ getFilteredScanDetailsForOpenUnsavedNetworks()617 public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() { 618 List<ScanDetail> openUnsavedNetworks = new ArrayList<>(); 619 boolean enhancedOpenSupported = isEnhancedOpenSupported(); 620 for (ScanDetail scanDetail : mFilteredNetworks) { 621 ScanResult scanResult = scanDetail.getScanResult(); 622 623 if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { 624 continue; 625 } 626 627 // Filter out Enhanced Open networks on devices that do not support it 628 if (ScanResultUtil.isScanResultForOweNetwork(scanResult) 629 && !enhancedOpenSupported) { 630 continue; 631 } 632 633 // Skip saved networks 634 if (mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail) != null) { 635 continue; 636 } 637 638 openUnsavedNetworks.add(scanDetail); 639 } 640 return openUnsavedNetworks; 641 } 642 643 /** 644 * @return the list of ScanDetails scored as potential candidates by the last run of 645 * selectNetwork, this will be empty if Network selector determined no selection was 646 * needed on last run. This includes scan details of sufficient signal strength, and 647 * had an associated WifiConfiguration. 648 */ getConnectableScanDetails()649 public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() { 650 return mConnectableNetworks; 651 } 652 653 /** 654 * Iterate thru the list of configured networks (includes all saved network configurations + 655 * any ephemeral network configurations created for passpoint networks, suggestions, carrier 656 * networks, etc) and do the following: 657 * a) Try to re-enable any temporarily enabled networks (if the blocklist duration has expired). 658 * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all 659 * of them to identify networks that are present in the current scan result. 660 * c) Log any disabled networks. 661 */ updateConfiguredNetworks()662 private void updateConfiguredNetworks() { 663 List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); 664 if (configuredNetworks.size() == 0) { 665 localLog("No configured networks."); 666 return; 667 } 668 669 StringBuffer sbuf = new StringBuffer(); 670 for (WifiConfiguration network : configuredNetworks) { 671 // If a configuration is temporarily disabled, re-enable it before trying 672 // to connect to it. 673 mWifiConfigManager.tryEnableNetwork(network.networkId); 674 // Clear the cached candidate, score and seen. 675 mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId); 676 677 // Log disabled network. 678 WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus(); 679 if (!status.isNetworkEnabled()) { 680 sbuf.append(" ").append(toNetworkString(network)).append(" "); 681 for (int index = WifiConfiguration.NetworkSelectionStatus 682 .NETWORK_SELECTION_DISABLED_STARTING_INDEX; 683 index < WifiConfiguration.NetworkSelectionStatus 684 .NETWORK_SELECTION_DISABLED_MAX; 685 index++) { 686 int count = status.getDisableReasonCounter(index); 687 // Here we log the reason as long as its count is greater than zero. The 688 // network may not be disabled because of this particular reason. Logging 689 // this information anyway to help understand what happened to the network. 690 if (count > 0) { 691 sbuf.append("reason=") 692 .append(WifiConfiguration.NetworkSelectionStatus 693 .getNetworkSelectionDisableReasonString(index)) 694 .append(", count=").append(count).append("; "); 695 } 696 } 697 sbuf.append("\n"); 698 } 699 } 700 701 if (sbuf.length() > 0) { 702 localLog("Disabled configured networks:"); 703 localLog(sbuf.toString()); 704 } 705 } 706 707 /** 708 * Overrides the {@code candidate} chosen by the {@link #mNominators} with the user chosen 709 * {@link WifiConfiguration} if one exists. 710 * 711 * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise 712 */ overrideCandidateWithUserConnectChoice( @onNull WifiConfiguration candidate)713 private WifiConfiguration overrideCandidateWithUserConnectChoice( 714 @NonNull WifiConfiguration candidate) { 715 WifiConfiguration tempConfig = Preconditions.checkNotNull(candidate); 716 WifiConfiguration originalCandidate = candidate; 717 ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate(); 718 719 Set<String> seenNetworks = new HashSet<>(); 720 seenNetworks.add(candidate.getProfileKey()); 721 722 while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) { 723 String key = tempConfig.getNetworkSelectionStatus().getConnectChoice(); 724 int userSelectedRssi = tempConfig.getNetworkSelectionStatus().getConnectChoiceRssi(); 725 tempConfig = mWifiConfigManager.getConfiguredNetwork(key); 726 727 if (tempConfig != null) { 728 if (seenNetworks.contains(tempConfig.getProfileKey())) { 729 Log.wtf(TAG, "user connected network is a loop, use candidate:" 730 + candidate); 731 mWifiConfigManager.setLegacyUserConnectChoice(candidate, 732 candidate.getNetworkSelectionStatus().getCandidate().level); 733 break; 734 } 735 seenNetworks.add(tempConfig.getProfileKey()); 736 WifiConfiguration.NetworkSelectionStatus tempStatus = 737 tempConfig.getNetworkSelectionStatus(); 738 boolean noInternetButInternetIsExpected = !tempConfig.isNoInternetAccessExpected() 739 && tempConfig.hasNoInternetAccess(); 740 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled() 741 && !noInternetButInternetIsExpected 742 && isUserChoiceRssiCloseToOrGreaterThanExpectedValue( 743 tempStatus.getCandidate().level, userSelectedRssi)) { 744 scanResultCandidate = tempStatus.getCandidate(); 745 candidate = tempConfig; 746 } 747 } else { 748 localLog("Connect choice: " + key + " has no corresponding saved config."); 749 break; 750 } 751 } 752 753 if (candidate != originalCandidate) { 754 localLog("After user selection adjustment, the final candidate is:" 755 + WifiNetworkSelector.toNetworkString(candidate) + " : " 756 + scanResultCandidate.BSSID); 757 mWifiMetrics.setNominatorForNetwork(candidate.networkId, 758 WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE); 759 } 760 return candidate; 761 } 762 isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi, int expectedRssi)763 private boolean isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi, 764 int expectedRssi) { 765 // The expectedRssi may be 0 for newly upgraded devices which do not have this information, 766 // pass the test for those devices to avoid regression. 767 if (expectedRssi == 0) { 768 return true; 769 } 770 return observedRssi >= expectedRssi - mScoringParams.getEstimateRssiErrorMargin(); 771 } 772 773 774 /** 775 * Indicates whether we have ever seen the network to be metered since wifi was enabled. 776 * 777 * This is sticky to prevent continuous flip-flopping between networks, when the metered 778 * status is learned after association. 779 */ isEverMetered(@onNull WifiConfiguration config, @Nullable WifiInfo info, @NonNull ScanDetail scanDetail)780 private boolean isEverMetered(@NonNull WifiConfiguration config, @Nullable WifiInfo info, 781 @NonNull ScanDetail scanDetail) { 782 // If info does not match config, don't use it. 783 if (info != null && info.getNetworkId() != config.networkId) info = null; 784 boolean metered = WifiConfiguration.isMetered(config, info); 785 NetworkDetail networkDetail = scanDetail.getNetworkDetail(); 786 if (networkDetail != null 787 && networkDetail.getAnt() 788 == NetworkDetail.Ant.ChargeablePublic) { 789 metered = true; 790 } 791 mWifiMetrics.addMeteredStat(config, metered); 792 if (config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE) { 793 // User override is in effect; we should trust it 794 if (mKnownMeteredNetworkIds.remove(config.networkId)) { 795 localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds); 796 } 797 metered = config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; 798 } else if (mKnownMeteredNetworkIds.contains(config.networkId)) { 799 // Use the saved information 800 metered = true; 801 } else if (metered) { 802 // Update the saved information 803 mKnownMeteredNetworkIds.add(config.networkId); 804 localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds); 805 } 806 return metered; 807 } 808 809 /** 810 * Returns the set of known metered network ids (for tests. dumpsys, and metrics). 811 */ getKnownMeteredNetworkIds()812 public Set<Integer> getKnownMeteredNetworkIds() { 813 return new ArraySet<>(mKnownMeteredNetworkIds); 814 } 815 816 private final ArraySet<Integer> mKnownMeteredNetworkIds = new ArraySet<>(); 817 818 819 /** 820 * Cleans up state that should go away when wifi is disabled. 821 */ resetOnDisable()822 public void resetOnDisable() { 823 mWifiConfigManager.clearLastSelectedNetwork(); 824 mKnownMeteredNetworkIds.clear(); 825 } 826 827 /** 828 * Container class for passing the ClientModeManager state for each instance that is managed by 829 * WifiConnectivityManager, i.e all {@link ClientModeManager#getRole()} equals 830 * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} or 831 * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}. 832 */ 833 public static class ClientModeManagerState { 834 /** Iface Name corresponding to iface (if known) */ 835 public final String ifaceName; 836 /** True if the device is connected */ 837 public final boolean connected; 838 /** True if the device is disconnected */ 839 public final boolean disconnected; 840 /** Currently connected network */ 841 public final WifiInfo wifiInfo; 842 ClientModeManagerState(@onNull ClientModeManager clientModeManager)843 ClientModeManagerState(@NonNull ClientModeManager clientModeManager) { 844 ifaceName = clientModeManager.getInterfaceName(); 845 connected = clientModeManager.isConnected(); 846 disconnected = clientModeManager.isDisconnected(); 847 wifiInfo = clientModeManager.syncRequestConnectionInfo(); 848 } 849 ClientModeManagerState()850 ClientModeManagerState() { 851 ifaceName = "unknown"; 852 connected = false; 853 disconnected = true; 854 wifiInfo = new WifiInfo(); 855 } 856 857 @VisibleForTesting ClientModeManagerState(@onNull String ifaceName, boolean connected, boolean disconnected, @NonNull WifiInfo wifiInfo)858 ClientModeManagerState(@NonNull String ifaceName, boolean connected, boolean disconnected, 859 @NonNull WifiInfo wifiInfo) { 860 this.ifaceName = ifaceName; 861 this.connected = connected; 862 this.disconnected = disconnected; 863 this.wifiInfo = wifiInfo; 864 } 865 866 @Override equals(Object that)867 public boolean equals(Object that) { 868 if (this == that) return true; 869 if (!(that instanceof ClientModeManagerState)) return false; 870 ClientModeManagerState thatCmmState = (ClientModeManagerState) that; 871 return Objects.equals(ifaceName, thatCmmState.ifaceName) 872 && connected == thatCmmState.connected 873 && disconnected == thatCmmState.disconnected 874 // Since wifiinfo does not have equals currently. 875 && Objects.equals(wifiInfo.getSSID(), thatCmmState.wifiInfo.getSSID()) 876 && Objects.equals(wifiInfo.getBSSID(), thatCmmState.wifiInfo.getBSSID()); 877 } 878 879 @Override hashCode()880 public int hashCode() { 881 return Objects.hash(ifaceName, connected, disconnected, 882 wifiInfo.getSSID(), wifiInfo.getBSSID()); 883 } 884 885 @Override toString()886 public String toString() { 887 return "ClientModeManagerState: " + ifaceName 888 + ", connection state: " 889 + (connected ? " connected" : (disconnected ? " disconnected" : "unknown")) 890 + ", WifiInfo: " + wifiInfo; 891 } 892 } 893 894 /** 895 * Returns the list of Candidates from networks in range. 896 * 897 * @param scanDetails List of ScanDetail for all the APs in range 898 * @param bssidBlocklist Blocked BSSIDs 899 * @param cmmStates State of all long lived client mode manager instances - 900 * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} & 901 * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}. 902 * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection 903 * @param oemPaidNetworkAllowed True if oem paid networks are allowed for connection 904 * @param oemPrivateNetworkAllowed True if oem private networks are allowed for connection 905 * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network 906 * @param multiInternetNetworkAllowed True if multi internet networks are allowed for 907 * connection. 908 * @return list of valid Candidate(s) 909 */ getCandidatesFromScan( @onNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist, @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, Set<Integer> restrictedNetworkAllowedUids, boolean multiInternetNetworkAllowed)910 public List<WifiCandidates.Candidate> getCandidatesFromScan( 911 @NonNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist, 912 @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed, 913 boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, 914 Set<Integer> restrictedNetworkAllowedUids, boolean multiInternetNetworkAllowed) { 915 mFilteredNetworks.clear(); 916 mConnectableNetworks.clear(); 917 if (scanDetails.size() == 0) { 918 localLog("Empty connectivity scan result"); 919 return null; 920 } 921 922 // Update the scan detail cache at the start, even if we skip network selection 923 updateScanDetailCache(scanDetails); 924 925 // Update the registered network nominators. 926 for (NetworkNominator registeredNominator : mNominators) { 927 registeredNominator.update(scanDetails); 928 } 929 930 // Shall we start network selection at all? 931 if (!multiInternetNetworkAllowed && !isNetworkSelectionNeeded(scanDetails, cmmStates)) { 932 return null; 933 } 934 935 // Filter out unwanted networks. 936 mFilteredNetworks = filterScanResults(scanDetails, bssidBlocklist, cmmStates); 937 if (mFilteredNetworks.size() == 0) { 938 return null; 939 } 940 941 WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext); 942 for (ClientModeManagerState cmmState : cmmStates) { 943 // Always get the current BSSID from WifiInfo in case that firmware initiated 944 // roaming happened. 945 String currentBssid = cmmState.wifiInfo.getBSSID(); 946 WifiConfiguration currentNetwork = 947 mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId()); 948 if (currentNetwork != null) { 949 wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid); 950 // We always want the current network to be a candidate so that it can participate. 951 // It may also get re-added by a nominator, in which case this fallback 952 // will be replaced. 953 MacAddress bssid = MacAddress.fromString(currentBssid); 954 SecurityParams params = currentNetwork.getNetworkSelectionStatus() 955 .getLastUsedSecurityParams(); 956 if (null == params) { 957 localLog("No known candidate security params for current network."); 958 continue; 959 } 960 WifiCandidates.Key key = new WifiCandidates.Key( 961 ScanResultMatchInfo.fromWifiConfiguration(currentNetwork), 962 bssid, currentNetwork.networkId, 963 params.getSecurityType()); 964 ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid); 965 int predictedTputMbps = (scanDetail == null) ? 0 : predictThroughput(scanDetail); 966 wifiCandidates.add(key, currentNetwork, 967 NetworkNominator.NOMINATOR_ID_CURRENT, 968 cmmState.wifiInfo.getRssi(), 969 cmmState.wifiInfo.getFrequency(), 970 ScanResult.CHANNEL_WIDTH_20MHZ, // channel width not available in WifiInfo 971 calculateLastSelectionWeight(currentNetwork.networkId), 972 WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo), 973 isFromCarrierOrPrivilegedApp(currentNetwork), 974 predictedTputMbps); 975 } 976 } 977 978 // Update all configured networks before initiating network selection. 979 updateConfiguredNetworks(); 980 981 for (NetworkNominator registeredNominator : mNominators) { 982 localLog("About to run " + registeredNominator.getName() + " :"); 983 registeredNominator.nominateNetworks( 984 new ArrayList<>(mFilteredNetworks), 985 untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed, 986 restrictedNetworkAllowedUids, (scanDetail, config) -> { 987 WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig( 988 scanDetail, config); 989 if (key != null) { 990 boolean metered = false; 991 for (ClientModeManagerState cmmState : cmmStates) { 992 if (isEverMetered(config, cmmState.wifiInfo, scanDetail)) { 993 metered = true; 994 break; 995 } 996 } 997 // TODO(b/151981920) Saved passpoint candidates are marked ephemeral 998 boolean added = wifiCandidates.add(key, config, 999 registeredNominator.getId(), 1000 scanDetail.getScanResult().level, 1001 scanDetail.getScanResult().frequency, 1002 scanDetail.getScanResult().channelWidth, 1003 calculateLastSelectionWeight(config.networkId), 1004 metered, 1005 isFromCarrierOrPrivilegedApp(config), 1006 predictThroughput(scanDetail)); 1007 if (added) { 1008 mConnectableNetworks.add(Pair.create(scanDetail, config)); 1009 mWifiConfigManager.updateScanDetailForNetwork( 1010 config.networkId, scanDetail); 1011 mWifiMetrics.setNominatorForNetwork(config.networkId, 1012 toProtoNominatorId(registeredNominator.getId())); 1013 } 1014 } 1015 }); 1016 } 1017 if (mConnectableNetworks.size() != wifiCandidates.size()) { 1018 localLog("Connectable: " + mConnectableNetworks.size() 1019 + " Candidates: " + wifiCandidates.size()); 1020 } 1021 return wifiCandidates.getCandidates(); 1022 } 1023 1024 /** 1025 * Add all results as candidates for the user selected network and let network selection 1026 * chooses the proper one for the user selected network. 1027 * @param config The configuration for the user selected network. 1028 * @param scanDetails List of ScanDetail for the user selected network. 1029 * @return list of valid Candidate(s) 1030 */ getCandidatesForUserSelection( WifiConfiguration config, @NonNull List<ScanDetail> scanDetails)1031 public List<WifiCandidates.Candidate> getCandidatesForUserSelection( 1032 WifiConfiguration config, @NonNull List<ScanDetail> scanDetails) { 1033 if (scanDetails.size() == 0) { 1034 if (mVerboseLoggingEnabled) { 1035 Log.d(TAG, "No scan result for the user selected network."); 1036 return null; 1037 } 1038 } 1039 1040 mConnectableNetworks.clear(); 1041 WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext); 1042 for (ScanDetail scanDetail: scanDetails) { 1043 WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig( 1044 scanDetail, config); 1045 if (null == key) continue; 1046 1047 boolean added = wifiCandidates.add(key, config, 1048 WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_CURRENT, 1049 scanDetail.getScanResult().level, 1050 scanDetail.getScanResult().frequency, 1051 scanDetail.getScanResult().channelWidth, 1052 0.0 /* lastSelectionWeightBetweenZeroAndOne */, 1053 false /* isMetered */, 1054 WifiNetworkSelector.isFromCarrierOrPrivilegedApp(config), 1055 0 /* predictedThroughputMbps */); 1056 if (!added) continue; 1057 1058 mConnectableNetworks.add(Pair.create(scanDetail, config)); 1059 mWifiConfigManager.updateScanDetailForNetwork( 1060 config.networkId, scanDetail); 1061 } 1062 return wifiCandidates.getCandidates(); 1063 } 1064 1065 /** 1066 * For transition networks with only legacy networks, 1067 * remove auto-upgrade type to use the legacy type to 1068 * avoid roaming issues between two types. 1069 */ removeAutoUpgradeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList, @WifiConfiguration.SecurityType int baseSecurityType, @WifiConfiguration.SecurityType int upgradableSecurityType, boolean isLegacyNetworkInRange, boolean isUpgradableTypeOnlyInRange, boolean isAutoUpgradeEnabled)1070 private void removeAutoUpgradeSecurityParamsIfNecessary( 1071 WifiConfiguration config, 1072 List<SecurityParams> scanResultParamsList, 1073 @WifiConfiguration.SecurityType int baseSecurityType, 1074 @WifiConfiguration.SecurityType int upgradableSecurityType, 1075 boolean isLegacyNetworkInRange, 1076 boolean isUpgradableTypeOnlyInRange, 1077 boolean isAutoUpgradeEnabled) { 1078 localLog("removeAutoUpgradeSecurityParamsIfNecessary:" 1079 + " SSID: " + config.SSID 1080 + " baseSecurityType: " + baseSecurityType 1081 + " upgradableSecurityType: " + upgradableSecurityType 1082 + " isLegacyNetworkInRange: " + isLegacyNetworkInRange 1083 + " isUpgradableTypeOnlyInRange: " + isUpgradableTypeOnlyInRange 1084 + " isAutoUpgradeEnabled: " + isAutoUpgradeEnabled); 1085 if (isAutoUpgradeEnabled) { 1086 // Consider removing the auto-upgraded type if legacy networks are in range. 1087 if (!isLegacyNetworkInRange) return; 1088 // If base params is disabled or removed, keep the auto-upgrade params. 1089 SecurityParams baseParams = config.getSecurityParams(baseSecurityType); 1090 if (null == baseParams || !baseParams.isEnabled()) return; 1091 // If there are APs with standalone-upgradeable security type is in range, 1092 // do not consider removing the auto-upgraded type. 1093 if (isUpgradableTypeOnlyInRange) return; 1094 } 1095 1096 SecurityParams upgradableParams = config.getSecurityParams(upgradableSecurityType); 1097 if (null == upgradableParams) return; 1098 if (!upgradableParams.isAddedByAutoUpgrade()) return; 1099 localLog("Remove upgradable security type " + upgradableSecurityType + " for the network."); 1100 scanResultParamsList.removeIf(p -> p.isSecurityType(upgradableSecurityType)); 1101 } 1102 1103 /** Helper function to place all conditions which need to remove auto-upgrade types. */ removeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList)1104 private void removeSecurityParamsIfNecessary( 1105 WifiConfiguration config, 1106 List<SecurityParams> scanResultParamsList) { 1107 // When offload is supported, both types are passed down. 1108 if (!mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) { 1109 removeAutoUpgradeSecurityParamsIfNecessary( 1110 config, scanResultParamsList, 1111 WifiConfiguration.SECURITY_TYPE_PSK, 1112 WifiConfiguration.SECURITY_TYPE_SAE, 1113 mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(config.SSID), 1114 mScanRequestProxy.isWpa3PersonalOnlyNetworkInRange(config.SSID), 1115 mWifiGlobals.isWpa3SaeUpgradeEnabled()); 1116 } 1117 removeAutoUpgradeSecurityParamsIfNecessary( 1118 config, scanResultParamsList, 1119 WifiConfiguration.SECURITY_TYPE_OPEN, 1120 WifiConfiguration.SECURITY_TYPE_OWE, 1121 mScanRequestProxy.isOpenOnlyNetworkInRange(config.SSID), 1122 mScanRequestProxy.isOweOnlyNetworkInRange(config.SSID), 1123 mWifiGlobals.isOweUpgradeEnabled()); 1124 removeAutoUpgradeSecurityParamsIfNecessary( 1125 config, scanResultParamsList, 1126 WifiConfiguration.SECURITY_TYPE_EAP, 1127 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE, 1128 mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(config.SSID), 1129 mScanRequestProxy.isWpa3EnterpriseOnlyNetworkInRange(config.SSID), 1130 true); 1131 // When using WPA3 (SAE), all passwords in all lengths are strings, but when using WPA2, 1132 // there is a distinction between 8-63 octets that go through BDKDF2 function, and 1133 // 64-octets that are assumed to be the output of it. BDKDF2 is not applicable to SAE 1134 // and to prevent interop issues with APs when 64-octet Hex PSK is configured, update 1135 // the configuration to use WPA2 only. 1136 WifiConfiguration configWithPassword = mWifiConfigManager 1137 .getConfiguredNetworkWithPassword(config.networkId); 1138 if (configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK) 1139 && configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) 1140 && !configWithPassword.preSharedKey.startsWith("\"") 1141 && configWithPassword.preSharedKey.length() == 64 1142 && configWithPassword.preSharedKey.matches("[0-9A-Fa-f]{64}")) { 1143 localLog("Remove SAE type for " + configWithPassword.SSID + " with 64-octet Hex PSK."); 1144 scanResultParamsList 1145 .removeIf(p -> p.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)); 1146 } 1147 } 1148 1149 /** 1150 * For the transition mode, MFPC should be true, and MFPR should be false, 1151 * see WPA3 SAE specification section 2.3 and 3.3. 1152 */ updateSecurityParamsForTransitionModeIfNecessary( ScanResult scanResult, SecurityParams params)1153 private void updateSecurityParamsForTransitionModeIfNecessary( 1154 ScanResult scanResult, SecurityParams params) { 1155 if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) 1156 && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult)) { 1157 params.setRequirePmf(false); 1158 } else if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE) 1159 && ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) { 1160 params.setRequirePmf(false); 1161 } 1162 } 1163 1164 /** 1165 * Update the candidate security params against a scan detail. 1166 * 1167 * @param network the target network. 1168 * @param scanDetail the target scan detail. 1169 */ updateNetworkCandidateSecurityParams( WifiConfiguration network, ScanDetail scanDetail)1170 private void updateNetworkCandidateSecurityParams( 1171 WifiConfiguration network, ScanDetail scanDetail) { 1172 if (network == null) return; 1173 if (scanDetail == null) return; 1174 1175 ScanResult scanResult = scanDetail.getScanResult(); 1176 List<SecurityParams> scanResultParamsList = ScanResultUtil 1177 .generateSecurityParamsListFromScanResult(scanResult); 1178 if (scanResultParamsList == null) return; 1179 // Under some conditions, the legacy type is preferred to have better 1180 // connectivity behaviors, and the auto-upgrade type should be removed. 1181 removeSecurityParamsIfNecessary(network, scanResultParamsList); 1182 SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams( 1183 network, 1184 scanResultParamsList); 1185 if (params == null) return; 1186 updateSecurityParamsForTransitionModeIfNecessary(scanResult, params); 1187 mWifiConfigManager.setNetworkCandidateScanResult( 1188 network.networkId, scanResult, 0, params); 1189 } 1190 1191 /** 1192 * Using the registered Scorers, choose the best network from the list of Candidate(s). 1193 * The ScanDetailCache is also updated here. 1194 * @param candidates - Candidates to perferm network selection on. 1195 * @return WifiConfiguration - the selected network, or null. 1196 */ 1197 @Nullable selectNetwork(@onNull List<WifiCandidates.Candidate> candidates)1198 public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates) { 1199 return selectNetwork(candidates, true); 1200 } 1201 1202 /** 1203 * Using the registered Scorers, choose the best network from the list of Candidate(s). 1204 * The ScanDetailCache is also updated here. 1205 * @param candidates - Candidates to perferm network selection on. 1206 * @param overrideEnabled If it is allowed to override candidate with User Connect Choice. 1207 * @return WifiConfiguration - the selected network, or null. 1208 */ 1209 @Nullable selectNetwork(@onNull List<WifiCandidates.Candidate> candidates, boolean overrideEnabled)1210 public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates, 1211 boolean overrideEnabled) { 1212 if (candidates == null || candidates.size() == 0) { 1213 return null; 1214 } 1215 WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext, candidates); 1216 final WifiCandidates.CandidateScorer activeScorer = getActiveCandidateScorer(); 1217 // Update the NetworkSelectionStatus in the configs for the current candidates 1218 // This is needed for the legacy user connect choice, at least 1219 Collection<Collection<WifiCandidates.Candidate>> groupedCandidates = 1220 wifiCandidates.getGroupedCandidates(); 1221 for (Collection<WifiCandidates.Candidate> group : groupedCandidates) { 1222 WifiCandidates.ScoredCandidate choice = activeScorer.scoreCandidates(group); 1223 if (choice == null) continue; 1224 ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey); 1225 if (scanDetail == null) continue; 1226 WifiConfiguration config = mWifiConfigManager 1227 .getConfiguredNetwork(choice.candidateKey.networkId); 1228 if (config == null) continue; 1229 updateNetworkCandidateSecurityParams(config, scanDetail); 1230 } 1231 1232 for (Collection<WifiCandidates.Candidate> group : groupedCandidates) { 1233 for (WifiCandidates.Candidate candidate : group.stream() 1234 .sorted((a, b) -> (b.getScanRssi() - a.getScanRssi())) // decreasing rssi 1235 .collect(Collectors.toList())) { 1236 localLog(candidate.toString()); 1237 } 1238 } 1239 1240 ArrayMap<Integer, Integer> experimentNetworkSelections = new ArrayMap<>(); // for metrics 1241 1242 int selectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID; 1243 1244 // Run all the CandidateScorers 1245 boolean legacyOverrideWanted = true; 1246 for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) { 1247 WifiCandidates.ScoredCandidate choice; 1248 try { 1249 choice = wifiCandidates.choose(candidateScorer); 1250 } catch (RuntimeException e) { 1251 Log.wtf(TAG, "Exception running a CandidateScorer", e); 1252 continue; 1253 } 1254 int networkId = choice.candidateKey == null 1255 ? WifiConfiguration.INVALID_NETWORK_ID 1256 : choice.candidateKey.networkId; 1257 String chooses = " would choose "; 1258 if (candidateScorer == activeScorer) { 1259 chooses = " chooses "; 1260 legacyOverrideWanted = choice.userConnectChoiceOverride; 1261 selectedNetworkId = networkId; 1262 updateChosenPasspointNetwork(choice); 1263 } 1264 String id = candidateScorer.getIdentifier(); 1265 int expid = experimentIdFromIdentifier(id); 1266 localLog(id + chooses + networkId 1267 + " score " + choice.value + "+/-" + choice.err 1268 + " expid " + expid); 1269 experimentNetworkSelections.put(expid, networkId); 1270 } 1271 1272 // Update metrics about differences in the selections made by various methods 1273 final int activeExperimentId = experimentIdFromIdentifier(activeScorer.getIdentifier()); 1274 for (Map.Entry<Integer, Integer> entry : 1275 experimentNetworkSelections.entrySet()) { 1276 int experimentId = entry.getKey(); 1277 if (experimentId == activeExperimentId) continue; 1278 int thisSelectedNetworkId = entry.getValue(); 1279 mWifiMetrics.logNetworkSelectionDecision(experimentId, activeExperimentId, 1280 selectedNetworkId == thisSelectedNetworkId, 1281 groupedCandidates.size()); 1282 } 1283 1284 // Get a fresh copy of WifiConfiguration reflecting any scan result updates 1285 WifiConfiguration selectedNetwork = 1286 mWifiConfigManager.getConfiguredNetwork(selectedNetworkId); 1287 if (selectedNetwork != null && legacyOverrideWanted && overrideEnabled) { 1288 selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork); 1289 } 1290 if (selectedNetwork != null) { 1291 mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis(); 1292 } 1293 return selectedNetwork; 1294 } 1295 1296 /** 1297 * Returns the ScanDetail given the candidate key, using the saved list of connectible networks. 1298 */ getScanDetailForCandidateKey(WifiCandidates.Key candidateKey)1299 private ScanDetail getScanDetailForCandidateKey(WifiCandidates.Key candidateKey) { 1300 if (candidateKey == null) return null; 1301 String bssid = candidateKey.bssid.toString(); 1302 for (Pair<ScanDetail, WifiConfiguration> pair : mConnectableNetworks) { 1303 if (candidateKey.networkId == pair.second.networkId 1304 && bssid.equals(pair.first.getBSSIDString())) { 1305 return pair.first; 1306 } 1307 } 1308 return null; 1309 } 1310 updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice)1311 private void updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice) { 1312 if (choice.candidateKey == null) { 1313 return; 1314 } 1315 WifiConfiguration config = 1316 mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId); 1317 if (config == null) { 1318 return; 1319 } 1320 if (config.isPasspoint()) { 1321 config.SSID = choice.candidateKey.matchInfo.networkSsid; 1322 mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName, 1323 false); 1324 } 1325 } 1326 updateScanDetailCache(List<ScanDetail> scanDetails)1327 private void updateScanDetailCache(List<ScanDetail> scanDetails) { 1328 for (ScanDetail scanDetail : scanDetails) { 1329 mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail); 1330 } 1331 } 1332 toProtoNominatorId(@etworkNominator.NominatorId int nominatorId)1333 private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) { 1334 switch (nominatorId) { 1335 case NetworkNominator.NOMINATOR_ID_SAVED: 1336 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED; 1337 case NetworkNominator.NOMINATOR_ID_SUGGESTION: 1338 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SUGGESTION; 1339 case NetworkNominator.NOMINATOR_ID_SCORED: 1340 return WifiMetricsProto.ConnectionEvent.NOMINATOR_EXTERNAL_SCORED; 1341 case NetworkNominator.NOMINATOR_ID_CURRENT: 1342 Log.e(TAG, "Unexpected NOMINATOR_ID_CURRENT", new RuntimeException()); 1343 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN; 1344 default: 1345 Log.e(TAG, "UnrecognizedNominatorId" + nominatorId); 1346 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN; 1347 } 1348 } 1349 calculateLastSelectionWeight(int networkId)1350 private double calculateLastSelectionWeight(int networkId) { 1351 if (networkId != mWifiConfigManager.getLastSelectedNetwork()) return 0.0; 1352 double timeDifference = mClock.getElapsedSinceBootMillis() 1353 - mWifiConfigManager.getLastSelectedTimeStamp(); 1354 long millis = TimeUnit.MINUTES.toMillis(mScoringParams.getLastSelectionMinutes()); 1355 if (timeDifference >= millis) return 0.0; 1356 double unclipped = 1.0 - (timeDifference / millis); 1357 return Math.min(Math.max(unclipped, 0.0), 1.0); 1358 } 1359 getActiveCandidateScorer()1360 private WifiCandidates.CandidateScorer getActiveCandidateScorer() { 1361 WifiCandidates.CandidateScorer ans = mCandidateScorers.get(PRESET_CANDIDATE_SCORER_NAME); 1362 int overrideExperimentId = mScoringParams.getExperimentIdentifier(); 1363 if (overrideExperimentId >= MIN_SCORER_EXP_ID) { 1364 for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) { 1365 int expId = experimentIdFromIdentifier(candidateScorer.getIdentifier()); 1366 if (expId == overrideExperimentId) { 1367 ans = candidateScorer; 1368 break; 1369 } 1370 } 1371 } 1372 if (ans == null && PRESET_CANDIDATE_SCORER_NAME != null) { 1373 Log.wtf(TAG, PRESET_CANDIDATE_SCORER_NAME + " is not registered!"); 1374 } 1375 mWifiMetrics.setNetworkSelectorExperimentId(ans == null 1376 ? LEGACY_CANDIDATE_SCORER_EXP_ID 1377 : experimentIdFromIdentifier(ans.getIdentifier())); 1378 return ans; 1379 } 1380 predictThroughput(@onNull ScanDetail scanDetail)1381 private int predictThroughput(@NonNull ScanDetail scanDetail) { 1382 if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) { 1383 return 0; 1384 } 1385 int channelUtilizationLinkLayerStats = BssLoad.INVALID; 1386 if (mWifiChannelUtilization != null) { 1387 channelUtilizationLinkLayerStats = 1388 mWifiChannelUtilization.getUtilizationRatio( 1389 scanDetail.getScanResult().frequency); 1390 } 1391 ClientModeManager primaryManager = 1392 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager(); 1393 return mThroughputPredictor.predictThroughput( 1394 primaryManager.getDeviceWiphyCapabilities(), 1395 scanDetail.getScanResult().getWifiStandard(), 1396 scanDetail.getScanResult().channelWidth, 1397 scanDetail.getScanResult().level, 1398 scanDetail.getScanResult().frequency, 1399 scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(), 1400 scanDetail.getNetworkDetail().getChannelUtilization(), 1401 channelUtilizationLinkLayerStats, 1402 mWifiGlobals.isBluetoothConnected()); 1403 } 1404 1405 /** 1406 * Register a network nominator 1407 * 1408 * @param nominator the network nominator to be registered 1409 */ registerNetworkNominator(@onNull NetworkNominator nominator)1410 public void registerNetworkNominator(@NonNull NetworkNominator nominator) { 1411 mNominators.add(Preconditions.checkNotNull(nominator)); 1412 } 1413 1414 /** 1415 * Register a candidate scorer. 1416 * 1417 * Replaces any existing scorer having the same identifier. 1418 */ registerCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1419 public void registerCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) { 1420 String name = Preconditions.checkNotNull(candidateScorer).getIdentifier(); 1421 if (name != null) { 1422 mCandidateScorers.put(name, candidateScorer); 1423 } 1424 } 1425 1426 /** 1427 * Unregister a candidate scorer. 1428 */ unregisterCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1429 public void unregisterCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) { 1430 String name = Preconditions.checkNotNull(candidateScorer).getIdentifier(); 1431 if (name != null) { 1432 mCandidateScorers.remove(name); 1433 } 1434 } 1435 1436 /** 1437 * Indicate whether or not a configuration is from carrier or privileged app. 1438 * 1439 * @param config The network configuration 1440 * @return true if this configuration is from carrier or privileged app; false otherwise. 1441 */ isFromCarrierOrPrivilegedApp(WifiConfiguration config)1442 public static boolean isFromCarrierOrPrivilegedApp(WifiConfiguration config) { 1443 if (config.fromWifiNetworkSuggestion 1444 && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) { 1445 // Privileged carrier suggestion 1446 return true; 1447 } 1448 if (config.isEphemeral() 1449 && !config.fromWifiNetworkSpecifier 1450 && !config.fromWifiNetworkSuggestion) { 1451 // From ScoredNetworkNominator 1452 return true; 1453 } 1454 return false; 1455 } 1456 1457 /** 1458 * Derives a numeric experiment identifier from a CandidateScorer's identifier. 1459 * 1460 * @returns a positive number that starts with the decimal digits ID_PREFIX 1461 */ experimentIdFromIdentifier(String id)1462 public static int experimentIdFromIdentifier(String id) { 1463 final int digits = (int) (((long) id.hashCode()) & Integer.MAX_VALUE) % ID_SUFFIX_MOD; 1464 return ID_PREFIX * ID_SUFFIX_MOD + digits; 1465 } 1466 1467 private static final int ID_SUFFIX_MOD = 1_000_000; 1468 private static final int ID_PREFIX = 42; 1469 private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD; 1470 WifiNetworkSelector( Context context, WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiConfigManager configManager, Clock clock, LocalLog localLog, WifiMetrics wifiMetrics, WifiInjector wifiInjector, ThroughputPredictor throughputPredictor, WifiChannelUtilization wifiChannelUtilization, WifiGlobals wifiGlobals, ScanRequestProxy scanRequestProxy)1471 WifiNetworkSelector( 1472 Context context, 1473 WifiScoreCard wifiScoreCard, 1474 ScoringParams scoringParams, 1475 WifiConfigManager configManager, 1476 Clock clock, 1477 LocalLog localLog, 1478 WifiMetrics wifiMetrics, 1479 WifiInjector wifiInjector, 1480 ThroughputPredictor throughputPredictor, 1481 WifiChannelUtilization wifiChannelUtilization, 1482 WifiGlobals wifiGlobals, 1483 ScanRequestProxy scanRequestProxy) { 1484 mContext = context; 1485 mWifiScoreCard = wifiScoreCard; 1486 mScoringParams = scoringParams; 1487 mWifiConfigManager = configManager; 1488 mClock = clock; 1489 mLocalLog = localLog; 1490 mWifiMetrics = wifiMetrics; 1491 mWifiInjector = wifiInjector; 1492 mThroughputPredictor = throughputPredictor; 1493 mWifiChannelUtilization = wifiChannelUtilization; 1494 mWifiGlobals = wifiGlobals; 1495 mScanRequestProxy = scanRequestProxy; 1496 } 1497 } 1498