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