• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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