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