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 
isEnhancedOpenSupported()465     private boolean isEnhancedOpenSupported() {
466         if (mIsEnhancedOpenSupportedInitialized) {
467             return mIsEnhancedOpenSupported;
468         }
469 
470         mIsEnhancedOpenSupportedInitialized = true;
471         mIsEnhancedOpenSupported = (mWifiNative.getSupportedFeatureSet(
472                 mWifiNative.getClientInterfaceName()) & WIFI_FEATURE_OWE) != 0;
473         return mIsEnhancedOpenSupported;
474     }
475 
476     /**
477      * This returns a list of ScanDetails that were filtered in the process of network selection.
478      * The list is further filtered for only open unsaved networks.
479      *
480      * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
481      * blacklisted BSSIDS, or low signal strength. This will return an empty list when there are
482      * no open unsaved networks, or when network selection has not been run.
483      */
getFilteredScanDetailsForOpenUnsavedNetworks()484     public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
485         List<ScanDetail> openUnsavedNetworks = new ArrayList<>();
486         boolean enhancedOpenSupported = isEnhancedOpenSupported();
487         for (ScanDetail scanDetail : mFilteredNetworks) {
488             ScanResult scanResult = scanDetail.getScanResult();
489 
490             if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
491                 continue;
492             }
493 
494             // Filter out Enhanced Open networks on devices that do not support it
495             if (ScanResultUtil.isScanResultForOweNetwork(scanResult)
496                     && !enhancedOpenSupported) {
497                 continue;
498             }
499 
500             // Skip saved networks
501             if (mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail) != null) {
502                 continue;
503             }
504 
505             openUnsavedNetworks.add(scanDetail);
506         }
507         return openUnsavedNetworks;
508     }
509 
510     /**
511      * @return the list of ScanDetails scored as potential candidates by the last run of
512      * selectNetwork, this will be empty if Network selector determined no selection was
513      * needed on last run. This includes scan details of sufficient signal strength, and
514      * had an associated WifiConfiguration.
515      */
getConnectableScanDetails()516     public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() {
517         return mConnectableNetworks;
518     }
519 
520     /**
521      * This API is called when user explicitly selects a network. Currently, it is used in following
522      * cases:
523      * (1) User explicitly chooses to connect to a saved network.
524      * (2) User saves a network after adding a new network.
525      * (3) User saves a network after modifying a saved network.
526      * Following actions will be triggered:
527      * 1. If this network is disabled, we need re-enable it again.
528      * 2. This network is favored over all the other networks visible in latest network
529      * selection procedure.
530      *
531      * @param netId ID for the network chosen by the user
532      * @return true -- There is change made to connection choice of any saved network.
533      * false -- There is no change made to connection choice of any saved network.
534      */
setUserConnectChoice(int netId)535     public boolean setUserConnectChoice(int netId) {
536         localLog("userSelectNetwork: network ID=" + netId);
537         WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId);
538 
539         if (selected == null || selected.SSID == null) {
540             localLog("userSelectNetwork: Invalid configuration with nid=" + netId);
541             return false;
542         }
543 
544         // Enable the network if it is disabled.
545         if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
546             mWifiConfigManager.updateNetworkSelectionStatus(netId,
547                     WifiConfiguration.NetworkSelectionStatus.DISABLED_NONE);
548         }
549         return setLegacyUserConnectChoice(selected);
550     }
551 
552     /**
553      * This maintains the legacy user connect choice state in the config store
554      */
setLegacyUserConnectChoice(@onNull final WifiConfiguration selected)555     private boolean setLegacyUserConnectChoice(@NonNull final WifiConfiguration selected) {
556         boolean change = false;
557         String key = selected.getKey();
558         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
559 
560         for (WifiConfiguration network : configuredNetworks) {
561             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
562             if (network.networkId == selected.networkId) {
563                 if (status.getConnectChoice() != null) {
564                     localLog("Remove user selection preference of " + status.getConnectChoice()
565                             + " from " + network.SSID + " : " + network.networkId);
566                     mWifiConfigManager.clearNetworkConnectChoice(network.networkId);
567                     change = true;
568                 }
569                 continue;
570             }
571 
572             if (status.getSeenInLastQualifiedNetworkSelection()
573                     && !key.equals(status.getConnectChoice())) {
574                 localLog("Add key: " + key + " to "
575                         + toNetworkString(network));
576                 mWifiConfigManager.setNetworkConnectChoice(network.networkId, key);
577                 change = true;
578             }
579         }
580 
581         return change;
582     }
583 
584 
585     /**
586      * Iterate thru the list of configured networks (includes all saved network configurations +
587      * any ephemeral network configurations created for passpoint networks, suggestions, carrier
588      * networks, etc) and do the following:
589      * a) Try to re-enable any temporarily enabled networks (if the blacklist duration has expired).
590      * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all
591      * of them to identify networks that are present in the current scan result.
592      * c) Log any disabled networks.
593      */
updateConfiguredNetworks()594     private void updateConfiguredNetworks() {
595         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
596         if (configuredNetworks.size() == 0) {
597             localLog("No configured networks.");
598             return;
599         }
600 
601         StringBuffer sbuf = new StringBuffer();
602         for (WifiConfiguration network : configuredNetworks) {
603             // If a configuration is temporarily disabled, re-enable it before trying
604             // to connect to it.
605             mWifiConfigManager.tryEnableNetwork(network.networkId);
606             // Clear the cached candidate, score and seen.
607             mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
608 
609             // Log disabled network.
610             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
611             if (!status.isNetworkEnabled()) {
612                 sbuf.append("  ").append(toNetworkString(network)).append(" ");
613                 for (int index = WifiConfiguration.NetworkSelectionStatus
614                         .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
615                         index < WifiConfiguration.NetworkSelectionStatus
616                                 .NETWORK_SELECTION_DISABLED_MAX;
617                         index++) {
618                     int count = status.getDisableReasonCounter(index);
619                     // Here we log the reason as long as its count is greater than zero. The
620                     // network may not be disabled because of this particular reason. Logging
621                     // this information anyway to help understand what happened to the network.
622                     if (count > 0) {
623                         sbuf.append("reason=")
624                                 .append(WifiConfiguration.NetworkSelectionStatus
625                                         .getNetworkSelectionDisableReasonString(index))
626                                 .append(", count=").append(count).append("; ");
627                     }
628                 }
629                 sbuf.append("\n");
630             }
631         }
632 
633         if (sbuf.length() > 0) {
634             localLog("Disabled configured networks:");
635             localLog(sbuf.toString());
636         }
637     }
638 
639     /**
640      * Overrides the {@code candidate} chosen by the {@link #mNominators} with the user chosen
641      * {@link WifiConfiguration} if one exists.
642      *
643      * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise
644      */
overrideCandidateWithUserConnectChoice( @onNull WifiConfiguration candidate)645     private WifiConfiguration overrideCandidateWithUserConnectChoice(
646             @NonNull WifiConfiguration candidate) {
647         WifiConfiguration tempConfig = Preconditions.checkNotNull(candidate);
648         WifiConfiguration originalCandidate = candidate;
649         ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
650 
651         while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
652             String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
653             tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
654 
655             if (tempConfig != null) {
656                 WifiConfiguration.NetworkSelectionStatus tempStatus =
657                         tempConfig.getNetworkSelectionStatus();
658                 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
659                     scanResultCandidate = tempStatus.getCandidate();
660                     candidate = tempConfig;
661                 }
662             } else {
663                 localLog("Connect choice: " + key + " has no corresponding saved config.");
664                 break;
665             }
666         }
667 
668         if (candidate != originalCandidate) {
669             localLog("After user selection adjustment, the final candidate is:"
670                     + WifiNetworkSelector.toNetworkString(candidate) + " : "
671                     + scanResultCandidate.BSSID);
672             mWifiMetrics.setNominatorForNetwork(candidate.networkId,
673                     WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE);
674         }
675         return candidate;
676     }
677 
678 
679     /**
680      * Indicates whether we have ever seen the network to be metered since wifi was enabled.
681      *
682      * This is sticky to prevent continuous flip-flopping between networks, when the metered
683      * status is learned after association.
684      */
isEverMetered(@onNull WifiConfiguration config, @Nullable WifiInfo info, @NonNull ScanDetail scanDetail)685     private boolean isEverMetered(@NonNull WifiConfiguration config, @Nullable WifiInfo info,
686             @NonNull ScanDetail scanDetail) {
687         // If info does not match config, don't use it.
688         if (info != null && info.getNetworkId() != config.networkId) info = null;
689         boolean metered = WifiConfiguration.isMetered(config, info);
690         NetworkDetail networkDetail = scanDetail.getNetworkDetail();
691         if (networkDetail != null
692                 && networkDetail.getAnt()
693                 == NetworkDetail.Ant.ChargeablePublic) {
694             metered = true;
695         }
696         mWifiMetrics.addMeteredStat(config, metered);
697         if (config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE) {
698             // User override is in effect; we should trust it
699             if (mKnownMeteredNetworkIds.remove(config.networkId)) {
700                 localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
701             }
702             metered = config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
703         } else if (mKnownMeteredNetworkIds.contains(config.networkId)) {
704             // Use the saved information
705             metered = true;
706         } else if (metered) {
707             // Update the saved information
708             mKnownMeteredNetworkIds.add(config.networkId);
709             localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
710         }
711         return metered;
712     }
713 
714     /**
715      * Returns the set of known metered network ids (for tests. dumpsys, and metrics).
716      */
getKnownMeteredNetworkIds()717     public Set<Integer> getKnownMeteredNetworkIds() {
718         return new ArraySet<>(mKnownMeteredNetworkIds);
719     }
720 
721     private final ArraySet<Integer> mKnownMeteredNetworkIds = new ArraySet<>();
722 
723 
724     /**
725      * Cleans up state that should go away when wifi is disabled.
726      */
resetOnDisable()727     public void resetOnDisable() {
728         mWifiConfigManager.clearLastSelectedNetwork();
729         mKnownMeteredNetworkIds.clear();
730     }
731 
732     /**
733      * Returns the list of Candidates from networks in range.
734      *
735      * @param scanDetails             List of ScanDetail for all the APs in range
736      * @param bssidBlacklist          Blacklisted BSSIDs
737      * @param wifiInfo                Currently connected network
738      * @param connected               True if the device is connected
739      * @param disconnected            True if the device is disconnected
740      * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection
741      * @return list of valid Candidate(s)
742      */
getCandidatesFromScan( List<ScanDetail> scanDetails, Set<String> bssidBlacklist, WifiInfo wifiInfo, boolean connected, boolean disconnected, boolean untrustedNetworkAllowed)743     public List<WifiCandidates.Candidate> getCandidatesFromScan(
744             List<ScanDetail> scanDetails, Set<String> bssidBlacklist, WifiInfo wifiInfo,
745             boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) {
746         mFilteredNetworks.clear();
747         mConnectableNetworks.clear();
748         if (scanDetails.size() == 0) {
749             localLog("Empty connectivity scan result");
750             return null;
751         }
752 
753         WifiConfiguration currentNetwork =
754                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
755 
756         // Always get the current BSSID from WifiInfo in case that firmware initiated
757         // roaming happened.
758         String currentBssid = wifiInfo.getBSSID();
759 
760         // Update the scan detail cache at the start, even if we skip network selection
761         updateScanDetailCache(scanDetails);
762 
763         // Shall we start network selection at all?
764         if (!isNetworkSelectionNeeded(scanDetails, wifiInfo, connected, disconnected)) {
765             return null;
766         }
767 
768         // Update all configured networks before initiating network selection.
769         updateConfiguredNetworks();
770 
771         // Update the registered network nominators.
772         for (NetworkNominator registeredNominator : mNominators) {
773             registeredNominator.update(scanDetails);
774         }
775 
776         // Filter out unwanted networks.
777         mFilteredNetworks = filterScanResults(scanDetails, bssidBlacklist,
778                 connected && wifiInfo.getScore() >= WIFI_POOR_SCORE, currentBssid);
779         if (mFilteredNetworks.size() == 0) {
780             return null;
781         }
782 
783         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
784         if (currentNetwork != null) {
785             wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid);
786             // We always want the current network to be a candidate so that it can participate.
787             // It may also get re-added by a nominator, in which case this fallback
788             // will be replaced.
789             MacAddress bssid = MacAddress.fromString(currentBssid);
790             WifiCandidates.Key key = new WifiCandidates.Key(
791                     ScanResultMatchInfo.fromWifiConfiguration(currentNetwork),
792                     bssid, currentNetwork.networkId);
793             wifiCandidates.add(key, currentNetwork,
794                     NetworkNominator.NOMINATOR_ID_CURRENT,
795                     wifiInfo.getRssi(),
796                     wifiInfo.getFrequency(),
797                     calculateLastSelectionWeight(currentNetwork.networkId),
798                     WifiConfiguration.isMetered(currentNetwork, wifiInfo),
799                     isFromCarrierOrPrivilegedApp(currentNetwork),
800                     0 /* Mbps */);
801         }
802         for (NetworkNominator registeredNominator : mNominators) {
803             localLog("About to run " + registeredNominator.getName() + " :");
804             registeredNominator.nominateNetworks(
805                     new ArrayList<>(mFilteredNetworks), currentNetwork, currentBssid, connected,
806                     untrustedNetworkAllowed,
807                     (scanDetail, config) -> {
808                         WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
809                                 scanDetail, config);
810                         if (key != null) {
811                             boolean metered = isEverMetered(config, wifiInfo, scanDetail);
812                             // TODO(b/151981920) Saved passpoint candidates are marked ephemeral
813                             boolean added = wifiCandidates.add(key, config,
814                                     registeredNominator.getId(),
815                                     scanDetail.getScanResult().level,
816                                     scanDetail.getScanResult().frequency,
817                                     calculateLastSelectionWeight(config.networkId),
818                                     metered,
819                                     isFromCarrierOrPrivilegedApp(config),
820                                     predictThroughput(scanDetail));
821                             if (added) {
822                                 mConnectableNetworks.add(Pair.create(scanDetail, config));
823                                 mWifiConfigManager.updateScanDetailForNetwork(
824                                         config.networkId, scanDetail);
825                                 mWifiMetrics.setNominatorForNetwork(config.networkId,
826                                         toProtoNominatorId(registeredNominator.getId()));
827                             }
828                         }
829                     });
830         }
831         if (mConnectableNetworks.size() != wifiCandidates.size()) {
832             localLog("Connectable: " + mConnectableNetworks.size()
833                     + " Candidates: " + wifiCandidates.size());
834         }
835         return wifiCandidates.getCandidates();
836     }
837 
838     /**
839      * Using the registered Scorers, choose the best network from the list of Candidate(s).
840      * The ScanDetailCache is also updated here.
841      * @param candidates - Candidates to perferm network selection on.
842      * @return WifiConfiguration - the selected network, or null.
843      */
844     @NonNull
selectNetwork(List<WifiCandidates.Candidate> candidates)845     public WifiConfiguration selectNetwork(List<WifiCandidates.Candidate> candidates) {
846         if (candidates == null || candidates.size() == 0) {
847             return null;
848         }
849         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext, candidates);
850         final WifiCandidates.CandidateScorer activeScorer = getActiveCandidateScorer();
851         // Update the NetworkSelectionStatus in the configs for the current candidates
852         // This is needed for the legacy user connect choice, at least
853         Collection<Collection<WifiCandidates.Candidate>> groupedCandidates =
854                 wifiCandidates.getGroupedCandidates();
855         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
856             WifiCandidates.ScoredCandidate choice = activeScorer.scoreCandidates(group);
857             if (choice == null) continue;
858             ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey);
859             if (scanDetail == null) continue;
860             mWifiConfigManager.setNetworkCandidateScanResult(choice.candidateKey.networkId,
861                     scanDetail.getScanResult(), 0);
862         }
863 
864         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
865             for (WifiCandidates.Candidate candidate : group.stream()
866                     .sorted((a, b) -> (b.getScanRssi() - a.getScanRssi())) // decreasing rssi
867                     .collect(Collectors.toList())) {
868                 localLog(candidate.toString());
869             }
870         }
871 
872         ArrayMap<Integer, Integer> experimentNetworkSelections = new ArrayMap<>(); // for metrics
873 
874         int selectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
875 
876         // Run all the CandidateScorers
877         boolean legacyOverrideWanted = true;
878         for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
879             WifiCandidates.ScoredCandidate choice;
880             try {
881                 choice = wifiCandidates.choose(candidateScorer);
882             } catch (RuntimeException e) {
883                 Log.wtf(TAG, "Exception running a CandidateScorer", e);
884                 continue;
885             }
886             int networkId = choice.candidateKey == null
887                     ? WifiConfiguration.INVALID_NETWORK_ID
888                     : choice.candidateKey.networkId;
889             String chooses = " would choose ";
890             if (candidateScorer == activeScorer) {
891                 chooses = " chooses ";
892                 legacyOverrideWanted = choice.userConnectChoiceOverride;
893                 selectedNetworkId = networkId;
894                 updateChosenPasspointNetwork(choice);
895             }
896             String id = candidateScorer.getIdentifier();
897             int expid = experimentIdFromIdentifier(id);
898             localLog(id + chooses + networkId
899                     + " score " + choice.value + "+/-" + choice.err
900                     + " expid " + expid);
901             experimentNetworkSelections.put(expid, networkId);
902         }
903 
904         // Update metrics about differences in the selections made by various methods
905         final int activeExperimentId = experimentIdFromIdentifier(activeScorer.getIdentifier());
906         for (Map.Entry<Integer, Integer> entry :
907                 experimentNetworkSelections.entrySet()) {
908             int experimentId = entry.getKey();
909             if (experimentId == activeExperimentId) continue;
910             int thisSelectedNetworkId = entry.getValue();
911             mWifiMetrics.logNetworkSelectionDecision(experimentId, activeExperimentId,
912                     selectedNetworkId == thisSelectedNetworkId,
913                     groupedCandidates.size());
914         }
915 
916         // Get a fresh copy of WifiConfiguration reflecting any scan result updates
917         WifiConfiguration selectedNetwork =
918                 mWifiConfigManager.getConfiguredNetwork(selectedNetworkId);
919         if (selectedNetwork != null && legacyOverrideWanted) {
920             selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork);
921         }
922         if (selectedNetwork != null) {
923             mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
924         }
925         return selectedNetwork;
926     }
927 
928     /**
929      * Returns the ScanDetail given the candidate key, using the saved list of connectible networks.
930      */
getScanDetailForCandidateKey(WifiCandidates.Key candidateKey)931     private ScanDetail getScanDetailForCandidateKey(WifiCandidates.Key candidateKey) {
932         if (candidateKey == null) return null;
933         String bssid = candidateKey.bssid.toString();
934         for (Pair<ScanDetail, WifiConfiguration> pair : mConnectableNetworks) {
935             if (candidateKey.networkId == pair.second.networkId
936                     && bssid.equals(pair.first.getBSSIDString())) {
937                 return pair.first;
938             }
939         }
940         return null;
941     }
942 
updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice)943     private void updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice) {
944         if (choice.candidateKey == null) {
945             return;
946         }
947         WifiConfiguration config =
948                 mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId);
949         if (config == null) {
950             return;
951         }
952         if (config.isPasspoint()) {
953             config.SSID = choice.candidateKey.matchInfo.networkSsid;
954             mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName);
955         }
956     }
957 
updateScanDetailCache(List<ScanDetail> scanDetails)958     private void updateScanDetailCache(List<ScanDetail> scanDetails) {
959         for (ScanDetail scanDetail : scanDetails) {
960             mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
961         }
962     }
963 
toProtoNominatorId(@etworkNominator.NominatorId int nominatorId)964     private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) {
965         switch (nominatorId) {
966             case NetworkNominator.NOMINATOR_ID_SAVED:
967                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED;
968             case NetworkNominator.NOMINATOR_ID_SUGGESTION:
969                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SUGGESTION;
970             case NetworkNominator.NOMINATOR_ID_SCORED:
971                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_EXTERNAL_SCORED;
972             case NetworkNominator.NOMINATOR_ID_CURRENT:
973                 Log.e(TAG, "Unexpected NOMINATOR_ID_CURRENT", new RuntimeException());
974                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
975             default:
976                 Log.e(TAG, "UnrecognizedNominatorId" + nominatorId);
977                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
978         }
979     }
980 
calculateLastSelectionWeight(int networkId)981     private double calculateLastSelectionWeight(int networkId) {
982         if (networkId != mWifiConfigManager.getLastSelectedNetwork()) return 0.0;
983         double timeDifference = mClock.getElapsedSinceBootMillis()
984                 - mWifiConfigManager.getLastSelectedTimeStamp();
985         long millis = TimeUnit.MINUTES.toMillis(mScoringParams.getLastSelectionMinutes());
986         if (timeDifference >= millis) return 0.0;
987         double unclipped = 1.0 - (timeDifference / millis);
988         return Math.min(Math.max(unclipped, 0.0), 1.0);
989     }
990 
getActiveCandidateScorer()991     private WifiCandidates.CandidateScorer getActiveCandidateScorer() {
992         WifiCandidates.CandidateScorer ans = mCandidateScorers.get(PRESET_CANDIDATE_SCORER_NAME);
993         int overrideExperimentId = mScoringParams.getExperimentIdentifier();
994         if (overrideExperimentId >= MIN_SCORER_EXP_ID) {
995             for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
996                 int expId = experimentIdFromIdentifier(candidateScorer.getIdentifier());
997                 if (expId == overrideExperimentId) {
998                     ans = candidateScorer;
999                     break;
1000                 }
1001             }
1002         }
1003         if (ans == null && PRESET_CANDIDATE_SCORER_NAME != null) {
1004             Log.wtf(TAG, PRESET_CANDIDATE_SCORER_NAME + " is not registered!");
1005         }
1006         mWifiMetrics.setNetworkSelectorExperimentId(ans == null
1007                 ? LEGACY_CANDIDATE_SCORER_EXP_ID
1008                 : experimentIdFromIdentifier(ans.getIdentifier()));
1009         return ans;
1010     }
1011 
predictThroughput(@onNull ScanDetail scanDetail)1012     private int predictThroughput(@NonNull ScanDetail scanDetail) {
1013         if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) {
1014             return 0;
1015         }
1016         int channelUtilizationLinkLayerStats = BssLoad.INVALID;
1017         if (mWifiChannelUtilization != null) {
1018             channelUtilizationLinkLayerStats =
1019                     mWifiChannelUtilization.getUtilizationRatio(
1020                             scanDetail.getScanResult().frequency);
1021         }
1022         return mThroughputPredictor.predictThroughput(
1023                 mWifiNative.getDeviceWiphyCapabilities(mWifiNative.getClientInterfaceName()),
1024                 scanDetail.getScanResult().getWifiStandard(),
1025                 scanDetail.getScanResult().channelWidth,
1026                 scanDetail.getScanResult().level,
1027                 scanDetail.getScanResult().frequency,
1028                 scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
1029                 scanDetail.getNetworkDetail().getChannelUtilization(),
1030                 channelUtilizationLinkLayerStats,
1031                 mIsBluetoothConnected);
1032     }
1033 
1034     /**
1035      * Register a network nominator
1036      *
1037      * @param nominator the network nominator to be registered
1038      */
registerNetworkNominator(@onNull NetworkNominator nominator)1039     public void registerNetworkNominator(@NonNull NetworkNominator nominator) {
1040         mNominators.add(Preconditions.checkNotNull(nominator));
1041     }
1042 
1043     /**
1044      * Register a candidate scorer.
1045      *
1046      * Replaces any existing scorer having the same identifier.
1047      */
registerCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1048     public void registerCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1049         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1050         if (name != null) {
1051             mCandidateScorers.put(name, candidateScorer);
1052         }
1053     }
1054 
1055     /**
1056      * Unregister a candidate scorer.
1057      */
unregisterCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1058     public void unregisterCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1059         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1060         if (name != null) {
1061             mCandidateScorers.remove(name);
1062         }
1063     }
1064 
isFromCarrierOrPrivilegedApp(WifiConfiguration config)1065     private static boolean isFromCarrierOrPrivilegedApp(WifiConfiguration config) {
1066         if (config.fromWifiNetworkSuggestion
1067                 && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
1068             // Privileged carrier suggestion
1069             return true;
1070         }
1071         if (config.isEphemeral()
1072                 && !config.fromWifiNetworkSpecifier
1073                 && !config.fromWifiNetworkSuggestion) {
1074             // From ScoredNetworkNominator
1075             return true;
1076         }
1077         return false;
1078     }
1079 
1080     /**
1081      * Derives a numeric experiment identifier from a CandidateScorer's identifier.
1082      *
1083      * @returns a positive number that starts with the decimal digits ID_PREFIX
1084      */
experimentIdFromIdentifier(String id)1085     public static int experimentIdFromIdentifier(String id) {
1086         final int digits = (int) (((long) id.hashCode()) & Integer.MAX_VALUE) % ID_SUFFIX_MOD;
1087         return ID_PREFIX * ID_SUFFIX_MOD + digits;
1088     }
1089 
1090     private static final int ID_SUFFIX_MOD = 1_000_000;
1091     private static final int ID_PREFIX = 42;
1092     private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD;
1093 
1094     /**
1095      * Set Wifi channel utilization calculated from link layer stats
1096      */
setWifiChannelUtilization(WifiChannelUtilization wifiChannelUtilization)1097     public void setWifiChannelUtilization(WifiChannelUtilization wifiChannelUtilization) {
1098         mWifiChannelUtilization = wifiChannelUtilization;
1099     }
1100 
1101     /**
1102      * Set whether bluetooth is in the connected state
1103      */
setBluetoothConnected(boolean isBlueToothConnected)1104     public void setBluetoothConnected(boolean isBlueToothConnected) {
1105         mIsBluetoothConnected = isBlueToothConnected;
1106     }
1107 
WifiNetworkSelector(Context context, WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiConfigManager configManager, Clock clock, LocalLog localLog, WifiMetrics wifiMetrics, WifiNative wifiNative, ThroughputPredictor throughputPredictor)1108     WifiNetworkSelector(Context context, WifiScoreCard wifiScoreCard, ScoringParams scoringParams,
1109             WifiConfigManager configManager, Clock clock, LocalLog localLog,
1110             WifiMetrics wifiMetrics, WifiNative wifiNative,
1111             ThroughputPredictor throughputPredictor) {
1112         mContext = context;
1113         mWifiConfigManager = configManager;
1114         mClock = clock;
1115         mWifiScoreCard = wifiScoreCard;
1116         mScoringParams = scoringParams;
1117         mLocalLog = localLog;
1118         mWifiMetrics = wifiMetrics;
1119         mWifiNative = wifiNative;
1120         mThroughputPredictor = throughputPredictor;
1121     }
1122 }
1123