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