• 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 import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_DISABLED;
21 import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED;
22 import static android.net.wifi.WifiNetworkSelectionConfig.ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE;
23 
24 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
25 
26 import android.annotation.IntDef;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.SuppressLint;
30 import android.app.admin.DevicePolicyManager;
31 import android.app.admin.WifiSsidPolicy;
32 import android.content.Context;
33 import android.net.MacAddress;
34 import android.net.wifi.ScanResult;
35 import android.net.wifi.SecurityParams;
36 import android.net.wifi.SupplicantState;
37 import android.net.wifi.WifiAnnotations;
38 import android.net.wifi.WifiConfiguration;
39 import android.net.wifi.WifiInfo;
40 import android.net.wifi.WifiNetworkSelectionConfig.AssociatedNetworkSelectionOverride;
41 import android.net.wifi.WifiSsid;
42 import android.net.wifi.util.ScanResultUtil;
43 import android.telephony.TelephonyManager;
44 import android.text.TextUtils;
45 import android.util.ArrayMap;
46 import android.util.ArraySet;
47 import android.util.LocalLog;
48 import android.util.Log;
49 import android.util.Pair;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.util.Preconditions;
53 import com.android.modules.utils.build.SdkLevel;
54 import com.android.server.wifi.hotspot2.NetworkDetail;
55 import com.android.server.wifi.proto.nano.WifiMetricsProto;
56 import com.android.server.wifi.util.InformationElementUtil.BssLoad;
57 import com.android.server.wifi.util.WifiPermissionsUtil;
58 import com.android.wifi.resources.R;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.ArrayList;
63 import java.util.Collection;
64 import java.util.Comparator;
65 import java.util.HashSet;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Objects;
69 import java.util.Set;
70 import java.util.StringJoiner;
71 import java.util.concurrent.TimeUnit;
72 import java.util.stream.Collectors;
73 
74 /**
75  * WifiNetworkSelector looks at all the connectivity scan results and
76  * runs all the nominators to find or create matching configurations.
77  * Then it makes a final selection from among the resulting candidates.
78  */
79 public class WifiNetworkSelector {
80     private static final String TAG = "WifiNetworkSelector";
81 
82     private static final long INVALID_TIME_STAMP = Long.MIN_VALUE;
83 
84     /**
85      * Minimum time gap between last successful network selection and a
86      * new selection attempt.
87      */
88     @VisibleForTesting
89     public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000;
90 
91     /**
92      * Connected score value used to decide whether a still-connected wifi should be treated
93      * as unconnected when filtering scan results.
94      */
95     @VisibleForTesting
96     public static final int WIFI_POOR_SCORE = ConnectedScore.WIFI_TRANSITION_SCORE - 10;
97 
98     /**
99      * The identifier string of the CandidateScorer to use (in the absence of overrides).
100      */
101     public static final String PRESET_CANDIDATE_SCORER_NAME = "ThroughputScorer";
102 
103     /**
104      * Experiment ID for the legacy scorer.
105      */
106     public static final int LEGACY_CANDIDATE_SCORER_EXP_ID = 0;
107 
108     private final Context mContext;
109     private final WifiConfigManager mWifiConfigManager;
110     private final Clock mClock;
111     private final LocalLog mLocalLog;
112     private boolean mVerboseLoggingEnabled = false;
113     private final WifiMetrics mWifiMetrics;
114     private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
115     // Buffer of filtered scan results (Scan results considered by network selection) & associated
116     // WifiConfiguration (if any).
117     private final List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks =
118             new ArrayList<>();
119     private List<ScanDetail> mFilteredNetworks = new ArrayList<>();
120     private final WifiScoreCard mWifiScoreCard;
121     private final ScoringParams mScoringParams;
122     private final WifiInjector mWifiInjector;
123     private final ThroughputPredictor mThroughputPredictor;
124     private final WifiChannelUtilization mWifiChannelUtilization;
125     private final WifiGlobals mWifiGlobals;
126     private final ScanRequestProxy mScanRequestProxy;
127 
128     private final Map<String, WifiCandidates.CandidateScorer> mCandidateScorers = new ArrayMap<>();
129     private boolean mIsEnhancedOpenSupportedInitialized = false;
130     private boolean mIsEnhancedOpenSupported;
131     private boolean mSufficiencyCheckEnabledWhenScreenOff  = true;
132     private boolean mSufficiencyCheckEnabledWhenScreenOn  = true;
133     private boolean mUserConnectChoiceOverrideEnabled = true;
134     private boolean mLastSelectionWeightEnabled = true;
135     private @AssociatedNetworkSelectionOverride int mAssociatedNetworkSelectionOverride =
136             ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE;
137     private boolean mScreenOn = false;
138     private final WifiNative mWifiNative;
139     private final DevicePolicyManager mDevicePolicyManager;
140 
141     /**
142      * Interface for WiFi Network Nominator
143      *
144      * A network nominator examines the scan results reports the
145      * connectable candidates in its category for further consideration.
146      */
147     public interface NetworkNominator {
148         /** Type of nominators */
149         int NOMINATOR_ID_SAVED = 0;
150         int NOMINATOR_ID_SUGGESTION = 1;
151         int NOMINATOR_ID_SCORED = 4;
152         int NOMINATOR_ID_CURRENT = 5; // Should always be last
153 
154         @IntDef(prefix = {"NOMINATOR_ID_"}, value = {
155                 NOMINATOR_ID_SAVED,
156                 NOMINATOR_ID_SUGGESTION,
157                 NOMINATOR_ID_SCORED,
158                 NOMINATOR_ID_CURRENT})
159         @Retention(RetentionPolicy.SOURCE)
160         public @interface NominatorId {
161         }
162 
163         /**
164          * Get the nominator type.
165          */
166         @NominatorId
getId()167         int getId();
168 
169         /**
170          * Get the nominator name.
171          */
getName()172         String getName();
173 
174         /**
175          * Update the nominator.
176          *
177          * Certain nominators have to be updated with the new scan results. For example
178          * the ScoredNetworkNominator needs to refresh its Score Cache.
179          *
180          * @param scanDetails a list of scan details constructed from the scan results
181          */
update(List<ScanDetail> scanDetails)182         void update(List<ScanDetail> scanDetails);
183 
184         /**
185          * Evaluate all the networks from the scan results.
186          *
187          * @param scanDetails                  a list of scan details constructed from the scan
188          *                                     results
189          * @param untrustedNetworkAllowed      a flag to indicate if untrusted networks are allowed
190          * @param oemPaidNetworkAllowed        a flag to indicate if oem paid networks are allowed
191          * @param oemPrivateNetworkAllowed     a flag to indicate if oem private networks are
192          *                                     allowed
193          * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network
194          * @param onConnectableListener        callback to record all of the connectable networks
195          */
nominateNetworks(@onNull List<ScanDetail> scanDetails, @NonNull List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates, boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, @NonNull Set<Integer> restrictedNetworkAllowedUids, @NonNull OnConnectableListener onConnectableListener)196         void nominateNetworks(@NonNull List<ScanDetail> scanDetails,
197                 @NonNull List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates,
198                 boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed,
199                 boolean oemPrivateNetworkAllowed,
200                 @NonNull Set<Integer> restrictedNetworkAllowedUids,
201                 @NonNull OnConnectableListener onConnectableListener);
202 
203         /**
204          * Callback for recording connectable candidates
205          */
206         public interface OnConnectableListener {
207             /**
208              * Notes that an access point is an eligible connection candidate
209              *
210              * @param scanDetail describes the specific access point
211              * @param config     is the WifiConfiguration for the network
212              */
onConnectable(ScanDetail scanDetail, WifiConfiguration config)213             void onConnectable(ScanDetail scanDetail, WifiConfiguration config);
214         }
215     }
216 
217     private final List<NetworkNominator> mNominators = new ArrayList<>(3);
218 
219     // A helper to log debugging information in the local log buffer, which can
220     // be retrieved in bugreport. It is also used to print the log in the console.
localLog(String log)221     private void localLog(String log) {
222         mLocalLog.log(log);
223         if (mVerboseLoggingEnabled) Log.d(TAG, log, null);
224     }
225 
226     /**
227      * Enable verbose logging in the console
228      */
enableVerboseLogging(boolean verbose)229     public void enableVerboseLogging(boolean verbose) {
230         mVerboseLoggingEnabled = verbose;
231     }
232 
233     /**
234      * Check if current network has sufficient RSSI
235      *
236      * @param wifiInfo info of currently connected network
237      * @return true if current link quality is sufficient, false otherwise.
238      */
hasSufficientLinkQuality(WifiInfo wifiInfo)239     public boolean hasSufficientLinkQuality(WifiInfo wifiInfo) {
240         int currentRssi = wifiInfo.getRssi();
241         return currentRssi >= mScoringParams.getSufficientRssi(wifiInfo.getFrequency());
242     }
243 
244     /**
245      * Check if current network has active Tx or Rx traffic
246      *
247      * @param wifiInfo info of currently connected network
248      * @return true if it has active Tx or Rx traffic, false otherwise.
249      */
hasActiveStream(WifiInfo wifiInfo)250     public boolean hasActiveStream(WifiInfo wifiInfo) {
251         return wifiInfo.getSuccessfulTxPacketsPerSecond()
252                 > mScoringParams.getActiveTrafficPacketsPerSecond()
253                 || wifiInfo.getSuccessfulRxPacketsPerSecond()
254                 > mScoringParams.getActiveTrafficPacketsPerSecond();
255     }
256 
257     /**
258      * Check if current network has internet or is expected to not have internet
259      */
hasInternetOrExpectNoInternet(WifiInfo wifiInfo)260     public boolean hasInternetOrExpectNoInternet(WifiInfo wifiInfo) {
261         WifiConfiguration network =
262                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
263         if (network == null) {
264             return false;
265         }
266         return !network.hasNoInternetAccess() || network.isNoInternetAccessExpected();
267     }
268     /**
269      * Determines whether the currently connected network is sufficient.
270      *
271      * If the network is good enough, or if switching to a new network is likely to
272      * be disruptive, we should avoid doing a network selection.
273      *
274      * @param wifiInfo info of currently connected network
275      * @return true if the network is sufficient
276      */
isNetworkSufficient(WifiInfo wifiInfo)277     public boolean isNetworkSufficient(WifiInfo wifiInfo) {
278         // Currently connected?
279         if (wifiInfo.getSupplicantState() != SupplicantState.COMPLETED) {
280             return false;
281         }
282 
283         localLog("Current connected network: " + wifiInfo.getNetworkId());
284 
285         WifiConfiguration network =
286                 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
287 
288         if (network == null) {
289             localLog("Current network was removed");
290             return false;
291         }
292 
293         // Skip autojoin for the first few seconds of a user-initiated connection.
294         // This delays network selection during the time that connectivity service may be posting
295         // a dialog about a no-internet network.
296         if (mWifiConfigManager.getLastSelectedNetwork() == network.networkId
297                 && (mClock.getElapsedSinceBootMillis()
298                     - mWifiConfigManager.getLastSelectedTimeStamp())
299                 <= mContext.getResources().getInteger(
300                     R.integer.config_wifiSufficientDurationAfterUserSelectionMilliseconds)) {
301             localLog("Current network is recently user-selected");
302             return true;
303         }
304 
305         // Set OSU (Online Sign Up) network for Passpoint Release 2 to sufficient
306         // so that network select selection is skipped and OSU process can complete.
307         if (network.osu) {
308             localLog("Current connection is OSU");
309             return true;
310         }
311 
312         // Current network is set as unusable by the external scorer.
313         if (!wifiInfo.isUsable()) {
314             localLog("Wifi is unusable according to external scorer.");
315             return false;
316         }
317 
318         // External scorer is not being used, and the current network's score is below the
319         // sufficient score threshold configured for the AOSP scorer.
320         if (!mWifiGlobals.isUsingExternalScorer() && wifiInfo.getScore()
321                 < mWifiGlobals.getWifiLowConnectedScoreThresholdToTriggerScanForMbb()) {
322             if (!SdkLevel.isAtLeastS()) {
323                 // Return false to prevent build issues since WifiInfo#isPrimary is only supported
324                 // on S and above.
325                 return false;
326             }
327             // Only return false to trigger network selection on the primary, since the secondary
328             // STA is not scored.
329             if (wifiInfo.isPrimary()) {
330                 return false;
331             }
332         }
333 
334         // OEM paid/private networks are only available to system apps, so this is never sufficient.
335         if (network.oemPaid || network.oemPrivate) {
336             localLog("Current network is oem paid/private");
337             return false;
338         }
339 
340         // Metered networks costs the user data, so this is insufficient.
341         if (WifiConfiguration.isMetered(network, wifiInfo)) {
342             localLog("Current network is metered");
343             return false;
344         }
345 
346         // Network without internet access is not sufficient, unless expected
347         if (!hasInternetOrExpectNoInternet(wifiInfo)) {
348             localLog("Current network has [" + network.numNoInternetAccessReports
349                     + "] no-internet access reports");
350             return false;
351         }
352 
353         if (!isSufficiencyCheckEnabled()) {
354             localLog("Current network assumed as insufficient because sufficiency check is "
355                     + "disabled. mScreenOn=" + mScreenOn);
356             return false;
357         }
358 
359         if (network.isIpProvisioningTimedOut()) {
360             localLog("Current network has no IPv4 provisioning and therefore insufficient");
361             return false;
362         }
363 
364         if (!hasSufficientLinkQuality(wifiInfo) && !hasActiveStream(wifiInfo)) {
365             localLog("Current network link quality is not sufficient and has low ongoing traffic");
366             return false;
367         }
368 
369         return true;
370     }
371 
372     /**
373      * Get whether associated network selection is enabled.
374      * @return
375      */
isAssociatedNetworkSelectionEnabled()376     public boolean isAssociatedNetworkSelectionEnabled() {
377         if (mAssociatedNetworkSelectionOverride == ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE) {
378             return mContext.getResources().getBoolean(
379                     R.bool.config_wifi_framework_enable_associated_network_selection);
380         }
381         return mAssociatedNetworkSelectionOverride == ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED;
382     }
383 
384     /**
385      * Check if network selection is needed on a CMM.
386      * @return True if network selection is needed. False if not needed.
387      */
isNetworkSelectionNeededForCmm(@onNull ClientModeManagerState cmmState)388     public boolean isNetworkSelectionNeededForCmm(@NonNull ClientModeManagerState cmmState) {
389         if (cmmState.connected) {
390             // Is roaming allowed?
391             if (!isAssociatedNetworkSelectionEnabled()) {
392                 localLog(cmmState.ifaceName + ": Switching networks in connected state is not "
393                         + "allowed. Skip network selection.");
394                 return false;
395             }
396 
397             // Has it been at least the minimum interval since last network selection?
398             if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) {
399                 long gap = mClock.getElapsedSinceBootMillis()
400                         - mLastNetworkSelectionTimeStamp;
401                 if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) {
402                     localLog(cmmState.ifaceName + ": Too short since last network selection: "
403                             + gap + " ms. Skip network selection.");
404                     return false;
405                 }
406             }
407             // Please note other scans (e.g., location scan or app scan) may also trigger network
408             // selection and these scans may or may not run sufficiency check.
409             // So it is better to run sufficiency check here before network selection.
410             if (isNetworkSufficient(cmmState.wifiInfo)) {
411                 localLog(cmmState.ifaceName
412                         + ": Current connected network already sufficient."
413                         + " Skip network selection.");
414                 return false;
415             } else {
416                 localLog(cmmState.ifaceName + ": Current connected network is not sufficient.");
417                 return true;
418             }
419         } else if (cmmState.disconnected || cmmState.ipProvisioningTimedOut) {
420             return true;
421         } else {
422             // No network selection if ClientModeImpl is in a state other than
423             // connected or disconnected (i.e connecting).
424             localLog(cmmState.ifaceName + ": ClientModeImpl is in neither CONNECTED nor "
425                     + "DISCONNECTED state. Skip network selection.");
426             return false;
427         }
428 
429     }
430 
isNetworkSelectionNeeded(@onNull List<ClientModeManagerState> cmmStates)431     private boolean isNetworkSelectionNeeded(@NonNull List<ClientModeManagerState> cmmStates) {
432         for (ClientModeManagerState cmmState : cmmStates) {
433             // network selection needed by this CMM instance, perform network selection
434             if (isNetworkSelectionNeededForCmm(cmmState)) {
435                 return true;
436             }
437         }
438         // none of the CMM instances need network selection, skip network selection.
439         return false;
440     }
441 
442     /**
443      * Format the given ScanResult as a scan ID for logging.
444      */
toScanId(@ullable ScanResult scanResult)445     public static String toScanId(@Nullable ScanResult scanResult) {
446         return scanResult == null ? "NULL"
447                 : scanResult.SSID + ":" + scanResult.BSSID;
448     }
449 
450     /**
451      * Format the given WifiConfiguration as a SSID:netId string
452      */
toNetworkString(WifiConfiguration network)453     public static String toNetworkString(WifiConfiguration network) {
454         if (network == null) {
455             return null;
456         }
457 
458         return (network.SSID + ":" + network.networkId);
459     }
460 
461     /**
462      * Compares ScanResult level against the minimum threshold for its band, returns true if lower
463      */
isSignalTooWeak(ScanResult scanResult)464     public boolean isSignalTooWeak(ScanResult scanResult) {
465         return (scanResult.level < mScoringParams.getEntryRssi(scanResult.frequency));
466     }
467 
468     @SuppressLint("NewApi")
filterScanResults(List<ScanDetail> scanDetails, Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates, int autojoinRestrictionSecurityTypes)469     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
470             Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates,
471             int autojoinRestrictionSecurityTypes) {
472         List<ScanDetail> validScanDetails = new ArrayList<>();
473         StringBuffer noValidSsid = new StringBuffer();
474         StringBuffer blockedBssid = new StringBuffer();
475         StringBuffer lowRssi = new StringBuffer();
476         StringBuffer mboAssociationDisallowedBssid = new StringBuffer();
477         StringBuffer adminRestrictedSsid = new StringBuffer();
478         StringJoiner deprecatedSecurityTypeSsid = new StringJoiner(" / ");
479         StringJoiner autojoinRestrictionSecurityTypesBssid = new StringJoiner(" / ");
480         List<String> currentBssids = cmmStates.stream()
481                 .map(cmmState -> cmmState.wifiInfo.getBSSID())
482                 .collect(Collectors.toList());
483         Set<String> scanResultPresentForCurrentBssids = new ArraySet<>();
484 
485         int adminMinimumSecurityLevel = 0;
486         boolean adminSsidRestrictionSet = false;
487         Set<WifiSsid> adminSsidAllowlist = new ArraySet<>();
488         Set<WifiSsid> admindSsidDenylist = new ArraySet<>();
489 
490         int numBssidFiltered = 0;
491 
492         if (mDevicePolicyManager != null && SdkLevel.isAtLeastT()) {
493             adminMinimumSecurityLevel =
494                     mDevicePolicyManager.getMinimumRequiredWifiSecurityLevel();
495             WifiSsidPolicy policy = mDevicePolicyManager.getWifiSsidPolicy();
496             if (policy != null) {
497                 adminSsidRestrictionSet = true;
498                 if (policy.getPolicyType() == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
499                     adminSsidAllowlist = policy.getSsids();
500                 } else {
501                     admindSsidDenylist = policy.getSsids();
502                 }
503             }
504         }
505 
506         for (ScanDetail scanDetail : scanDetails) {
507             ScanResult scanResult = scanDetail.getScanResult();
508 
509             if (TextUtils.isEmpty(scanResult.SSID)) {
510                 noValidSsid.append(scanResult.BSSID).append(" / ");
511                 continue;
512             }
513 
514             // Check if the scan results contain the currently connected BSSID's
515             if (currentBssids.contains(scanResult.BSSID)) {
516                 scanResultPresentForCurrentBssids.add(scanResult.BSSID);
517                 validScanDetails.add(scanDetail);
518                 continue;
519             }
520 
521             final String scanId = toScanId(scanResult);
522 
523             if (bssidBlocklist.contains(scanResult.BSSID)) {
524                 blockedBssid.append(scanId).append(" / ");
525                 numBssidFiltered++;
526                 continue;
527             }
528 
529             // Skip network with too weak signals.
530             if (isSignalTooWeak(scanResult)) {
531                 lowRssi.append(scanId);
532                 if (scanResult.is24GHz()) {
533                     lowRssi.append("(2.4GHz)");
534                 } else if (scanResult.is5GHz()) {
535                     lowRssi.append("(5GHz)");
536                 } else if (scanResult.is6GHz()) {
537                     lowRssi.append("(6GHz)");
538                 }
539                 lowRssi.append(scanResult.level).append(" / ");
540                 continue;
541             }
542 
543             // Skip BSS which is not accepting new connections.
544             NetworkDetail networkDetail = scanDetail.getNetworkDetail();
545             if (networkDetail != null) {
546                 if (networkDetail.getMboAssociationDisallowedReasonCode()
547                         != MboOceConstants.MBO_OCE_ATTRIBUTE_NOT_PRESENT) {
548                     mWifiMetrics
549                             .incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd();
550                     mboAssociationDisallowedBssid.append(scanId).append("(")
551                             .append(networkDetail.getMboAssociationDisallowedReasonCode())
552                             .append(")").append(" / ");
553                     continue;
554                 }
555             }
556 
557             // Skip network that does not meet the admin set SSID restriction
558             if (adminSsidRestrictionSet) {
559                 WifiSsid ssid = scanResult.getWifiSsid();
560                 // Allowlist policy set but network is not present in the list
561                 if (!adminSsidAllowlist.isEmpty() && !adminSsidAllowlist.contains(ssid)) {
562                     adminRestrictedSsid.append(scanId).append(" / ");
563                     continue;
564                 }
565                 // Denylist policy set but network is present in the list
566                 if (!admindSsidDenylist.isEmpty() && admindSsidDenylist.contains(ssid)) {
567                     adminRestrictedSsid.append(scanId).append(" / ");
568                     continue;
569                 }
570             }
571 
572             // Skip network that does not meet the admin set minimum security level restriction
573             if (adminMinimumSecurityLevel != 0) {
574                 boolean securityRestrictionPassed = false;
575                 @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes();
576                 for (int type : securityTypes) {
577                     int securityLevel = WifiInfo.convertSecurityTypeToDpmWifiSecurity(type);
578 
579                     // Skip unknown security type since security level cannot be determined.
580                     // If all the security types are unknown when the minimum security level
581                     // restriction is set, the scan result is ignored.
582                     if (securityLevel == WifiInfo.DPM_SECURITY_TYPE_UNKNOWN) continue;
583 
584                     if (adminMinimumSecurityLevel <= securityLevel) {
585                         securityRestrictionPassed = true;
586                         break;
587                     }
588                 }
589                 if (!securityRestrictionPassed) {
590                     adminRestrictedSsid.append(scanId).append(" / ");
591                     continue;
592                 }
593             }
594 
595             // Skip network that has deprecated security type
596             if (mWifiGlobals.isWpaPersonalDeprecated() || mWifiGlobals.isWepDeprecated()) {
597                 boolean securityTypeDeprecated = false;
598                 @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes();
599                 for (int type : securityTypes) {
600                     if (mWifiGlobals.isWepDeprecated() && type == WifiInfo.SECURITY_TYPE_WEP) {
601                         securityTypeDeprecated = true;
602                         break;
603                     }
604                     if (mWifiGlobals.isWpaPersonalDeprecated() && type == WifiInfo.SECURITY_TYPE_PSK
605                             && ScanResultUtil.isScanResultForWpaPersonalOnlyNetwork(scanResult)) {
606                         securityTypeDeprecated = true;
607                         break;
608                     }
609                 }
610                 if (securityTypeDeprecated) {
611                     deprecatedSecurityTypeSsid.add(scanId);
612                     continue;
613                 }
614             }
615 
616             // Skip network with security type that is restricted to auto join
617             if (autojoinRestrictionSecurityTypes != 0/*restrict none*/) {
618                 @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes();
619                 boolean securityTypeRestricted = true;
620                 for (int type : securityTypes) {
621                     if (((0x1 << type) & autojoinRestrictionSecurityTypes) == 0) {
622                         securityTypeRestricted = false;
623                         break;
624                     }
625                 }
626                 if (securityTypeRestricted) {
627                     autojoinRestrictionSecurityTypesBssid.add(scanId);
628                     continue;
629                 }
630             }
631 
632             validScanDetails.add(scanDetail);
633         }
634         mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(numBssidFiltered);
635 
636         // WNS listens to all single scan results. Some scan requests may not include
637         // the channel of the currently connected network, so the currently connected
638         // network won't show up in the scan results. We don't act on these scan results
639         // to avoid aggressive network switching which might trigger disconnection.
640         // TODO(b/147751334) this may no longer be needed
641         for (ClientModeManagerState cmmState : cmmStates) {
642             // TODO (b/169413079): Disable network selection on corresponding CMM instead.
643             if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE
644                     && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) {
645                 if (isSufficiencyCheckEnabled()) {
646                     localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID()
647                             + " is not in the scan results. Skip network selection.");
648                     validScanDetails.clear();
649                     return validScanDetails;
650                 } else {
651                     localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID()
652                             + " is not in the scan results. But continue network selection because"
653                             + " sufficiency check is disabled.");
654                 }
655             }
656         }
657 
658         if (noValidSsid.length() != 0) {
659             localLog("Networks filtered out due to invalid SSID: " + noValidSsid);
660         }
661 
662         if (blockedBssid.length() != 0) {
663             localLog("Networks filtered out due to blocklist: " + blockedBssid);
664         }
665 
666         if (lowRssi.length() != 0) {
667             localLog("Networks filtered out due to low signal strength: " + lowRssi);
668         }
669 
670         if (mboAssociationDisallowedBssid.length() != 0) {
671             localLog("Networks filtered out due to mbo association disallowed indication: "
672                     + mboAssociationDisallowedBssid);
673         }
674 
675         if (adminRestrictedSsid.length() != 0) {
676             localLog("Networks filtered out due to admin restrictions: " + adminRestrictedSsid);
677         }
678 
679         if (deprecatedSecurityTypeSsid.length() != 0) {
680             localLog("Networks filtered out due to deprecated security type: "
681                     + deprecatedSecurityTypeSsid);
682         }
683 
684         if (autojoinRestrictionSecurityTypesBssid.length() != 0) {
685             localLog("Networks filtered out due to auto join restriction on the security type: "
686                     + autojoinRestrictionSecurityTypesBssid);
687         }
688 
689         return validScanDetails;
690     }
691 
findScanDetailForBssid(List<ScanDetail> scanDetails, String currentBssid)692     private ScanDetail findScanDetailForBssid(List<ScanDetail> scanDetails,
693             String currentBssid) {
694         for (ScanDetail scanDetail : scanDetails) {
695             ScanResult scanResult = scanDetail.getScanResult();
696             if (scanResult.BSSID.equals(currentBssid)) {
697                 return scanDetail;
698             }
699         }
700         return null;
701     }
702 
isEnhancedOpenSupported()703     private boolean isEnhancedOpenSupported() {
704         if (mIsEnhancedOpenSupportedInitialized) {
705             return mIsEnhancedOpenSupported;
706         }
707 
708         mIsEnhancedOpenSupportedInitialized = true;
709         ClientModeManager primaryManager =
710                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
711         mIsEnhancedOpenSupported = primaryManager
712                 .getSupportedFeaturesBitSet().get(WIFI_FEATURE_OWE);
713         return mIsEnhancedOpenSupported;
714     }
715 
716     /**
717      * This returns a list of ScanDetails that were filtered in the process of network selection.
718      * The list is further filtered for only open unsaved networks.
719      *
720      * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
721      * blocked BSSIDS, or low signal strength. This will return an empty list when there are
722      * no open unsaved networks, or when network selection has not been run.
723      */
getFilteredScanDetailsForOpenUnsavedNetworks()724     public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
725         List<ScanDetail> openUnsavedNetworks = new ArrayList<>();
726         boolean enhancedOpenSupported = isEnhancedOpenSupported();
727         for (ScanDetail scanDetail : mFilteredNetworks) {
728             ScanResult scanResult = scanDetail.getScanResult();
729 
730             if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
731                 continue;
732             }
733 
734             // Filter out Enhanced Open networks on devices that do not support it
735             if (ScanResultUtil.isScanResultForOweNetwork(scanResult)
736                     && !enhancedOpenSupported) {
737                 continue;
738             }
739 
740             // Skip saved networks
741             if (mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail) != null) {
742                 continue;
743             }
744 
745             openUnsavedNetworks.add(scanDetail);
746         }
747         return openUnsavedNetworks;
748     }
749 
750     /**
751      * @return the list of ScanDetails scored as potential candidates by the last run of
752      * selectNetwork, this will be empty if Network selector determined no selection was
753      * needed on last run. This includes scan details of sufficient signal strength, and
754      * had an associated WifiConfiguration.
755      */
getConnectableScanDetails()756     public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() {
757         return mConnectableNetworks;
758     }
759 
760     /**
761      * Iterate thru the list of configured networks (includes all saved network configurations +
762      * any ephemeral network configurations created for passpoint networks, suggestions, carrier
763      * networks, etc) and do the following:
764      * a) Try to re-enable any temporarily enabled networks (if the blocklist duration has expired).
765      * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all
766      * of them to identify networks that are present in the current scan result.
767      * c) Log any disabled networks.
768      */
updateConfiguredNetworks()769     private void updateConfiguredNetworks() {
770         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
771         if (configuredNetworks.size() == 0) {
772             localLog("No configured networks.");
773             return;
774         }
775 
776         StringBuffer sbuf = new StringBuffer();
777         for (WifiConfiguration network : configuredNetworks) {
778             // If a configuration is temporarily disabled, re-enable it before trying
779             // to connect to it.
780             mWifiConfigManager.tryEnableNetwork(network.networkId);
781             // Clear the cached candidate, score and seen.
782             mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
783 
784             // Log disabled network.
785             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
786             if (!status.isNetworkEnabled()) {
787                 sbuf.append("  ").append(toNetworkString(network)).append(" ");
788                 for (int index = WifiConfiguration.NetworkSelectionStatus
789                         .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
790                         index < WifiConfiguration.NetworkSelectionStatus
791                                 .NETWORK_SELECTION_DISABLED_MAX;
792                         index++) {
793                     int count = status.getDisableReasonCounter(index);
794                     // Here we log the reason as long as its count is greater than zero. The
795                     // network may not be disabled because of this particular reason. Logging
796                     // this information anyway to help understand what happened to the network.
797                     if (count > 0) {
798                         sbuf.append("reason=")
799                                 .append(WifiConfiguration.NetworkSelectionStatus
800                                         .getNetworkSelectionDisableReasonString(index))
801                                 .append(", count=").append(count).append("; ");
802                     }
803                 }
804                 sbuf.append("\n");
805             }
806         }
807 
808         if (sbuf.length() > 0) {
809             localLog("Disabled configured networks:");
810             localLog(sbuf.toString());
811         }
812     }
813 
814     /**
815      * Overrides the {@code candidate} chosen by the {@link #mNominators} with the user chosen
816      * {@link WifiConfiguration} if one exists.
817      *
818      * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise
819      */
overrideCandidateWithUserConnectChoice( @onNull WifiConfiguration candidate)820     private WifiConfiguration overrideCandidateWithUserConnectChoice(
821             @NonNull WifiConfiguration candidate) {
822         WifiConfiguration tempConfig = Preconditions.checkNotNull(candidate);
823         WifiConfiguration originalCandidate = candidate;
824         ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
825 
826         Set<String> seenNetworks = new HashSet<>();
827         seenNetworks.add(candidate.getProfileKey());
828 
829         while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
830             String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
831             int userSelectedRssi = tempConfig.getNetworkSelectionStatus().getConnectChoiceRssi();
832             tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
833 
834             if (tempConfig != null) {
835                 if (seenNetworks.contains(tempConfig.getProfileKey())) {
836                     Log.wtf(TAG, "user connected network is a loop, use candidate:"
837                             + candidate);
838                     if (candidate.getNetworkSelectionStatus().getCandidate() != null) {
839                         mWifiConfigManager.setLegacyUserConnectChoice(candidate,
840                                 candidate.getNetworkSelectionStatus().getCandidate().level);
841                     }
842                     break;
843                 }
844                 seenNetworks.add(tempConfig.getProfileKey());
845                 WifiConfiguration.NetworkSelectionStatus tempStatus =
846                         tempConfig.getNetworkSelectionStatus();
847                 boolean noInternetButInternetIsExpected = !tempConfig.isNoInternetAccessExpected()
848                         && tempConfig.hasNoInternetAccess();
849                 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()
850                         && !noInternetButInternetIsExpected
851                         && isUserChoiceRssiCloseToOrGreaterThanExpectedValue(
852                                 tempStatus.getCandidate().level, userSelectedRssi)) {
853                     scanResultCandidate = tempStatus.getCandidate();
854                     candidate = tempConfig;
855                 }
856             } else {
857                 localLog("Connect choice: " + key + " has no corresponding saved config.");
858                 break;
859             }
860         }
861 
862         if (candidate != originalCandidate) {
863             localLog("After user selection adjustment, the final candidate is:"
864                     + WifiNetworkSelector.toNetworkString(candidate) + " : "
865                     + scanResultCandidate.BSSID);
866             mWifiMetrics.setNominatorForNetwork(candidate.networkId,
867                     WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE);
868         }
869         return candidate;
870     }
871 
isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi, int expectedRssi)872     private boolean isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi,
873             int expectedRssi) {
874         // The expectedRssi may be 0 for newly upgraded devices which do not have this information,
875         // pass the test for those devices to avoid regression.
876         if (expectedRssi == 0) {
877             return true;
878         }
879         return observedRssi >= expectedRssi - mScoringParams.getEstimateRssiErrorMargin();
880     }
881 
882 
883     /**
884      * Indicates whether we have ever seen the network to be metered since wifi was enabled.
885      *
886      * This is sticky to prevent continuous flip-flopping between networks, when the metered
887      * status is learned after association.
888      */
isEverMetered(@onNull WifiConfiguration config, @Nullable WifiInfo info, @NonNull ScanDetail scanDetail)889     private boolean isEverMetered(@NonNull WifiConfiguration config, @Nullable WifiInfo info,
890             @NonNull ScanDetail scanDetail) {
891         // If info does not match config, don't use it.
892         if (info != null && info.getNetworkId() != config.networkId) info = null;
893         boolean metered = WifiConfiguration.isMetered(config, info);
894         NetworkDetail networkDetail = scanDetail.getNetworkDetail();
895         if (networkDetail != null
896                 && networkDetail.getAnt()
897                 == NetworkDetail.Ant.ChargeablePublic) {
898             metered = true;
899         }
900         mWifiMetrics.addMeteredStat(config, metered);
901         if (config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE) {
902             // User override is in effect; we should trust it
903             if (mKnownMeteredNetworkIds.remove(config.networkId)) {
904                 localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
905             }
906             metered = config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
907         } else if (mKnownMeteredNetworkIds.contains(config.networkId)) {
908             // Use the saved information
909             metered = true;
910         } else if (metered) {
911             // Update the saved information
912             mKnownMeteredNetworkIds.add(config.networkId);
913             localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
914         }
915         return metered;
916     }
917 
918     /**
919      * Returns the set of known metered network ids (for tests. dumpsys, and metrics).
920      */
getKnownMeteredNetworkIds()921     public Set<Integer> getKnownMeteredNetworkIds() {
922         return new ArraySet<>(mKnownMeteredNetworkIds);
923     }
924 
925     private final ArraySet<Integer> mKnownMeteredNetworkIds = new ArraySet<>();
926 
927 
928     /**
929      * Cleans up state that should go away when wifi is disabled.
930      */
resetOnDisable()931     public void resetOnDisable() {
932         mWifiConfigManager.clearLastSelectedNetwork();
933         mKnownMeteredNetworkIds.clear();
934     }
935 
936     /**
937      * Container class for passing the ClientModeManager state for each instance that is managed by
938      * WifiConnectivityManager, i.e all {@link ClientModeManager#getRole()} equals
939      * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} or
940      * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
941      */
942     public static class ClientModeManagerState {
943         /** Iface Name corresponding to iface (if known) */
944         public final String ifaceName;
945         /** True if the device is connected */
946         public final boolean connected;
947         /** True if the device is disconnected */
948         public final boolean disconnected;
949         /** True if the device is connected in local-only mode due to ip provisioning timeout**/
950         public final boolean ipProvisioningTimedOut;
951          /** Currently connected network */
952         public final WifiInfo wifiInfo;
953         public final ActiveModeManager.ClientRole role;
954 
ClientModeManagerState(@onNull ClientModeManager clientModeManager)955         ClientModeManagerState(@NonNull ClientModeManager clientModeManager) {
956             ifaceName = clientModeManager.getInterfaceName();
957             connected = clientModeManager.isConnected();
958             disconnected = clientModeManager.isDisconnected();
959             ipProvisioningTimedOut = clientModeManager.isIpProvisioningTimedOut();
960             wifiInfo = clientModeManager.getConnectionInfo();
961             role = clientModeManager.getRole();
962         }
963 
ClientModeManagerState()964         ClientModeManagerState() {
965             ifaceName = "unknown";
966             connected = false;
967             disconnected = true;
968             wifiInfo = new WifiInfo();
969             ipProvisioningTimedOut = false;
970             role = null;
971         }
972 
973         @VisibleForTesting
ClientModeManagerState(@onNull String ifaceName, boolean connected, boolean disconnected, @NonNull WifiInfo wifiInfo, boolean ipProvisioningTimedOut, ActiveModeManager.ClientRole role)974         ClientModeManagerState(@NonNull String ifaceName, boolean connected, boolean disconnected,
975                 @NonNull WifiInfo wifiInfo, boolean ipProvisioningTimedOut,
976                 ActiveModeManager.ClientRole role) {
977             this.ifaceName = ifaceName;
978             this.connected = connected;
979             this.disconnected = disconnected;
980             this.wifiInfo = wifiInfo;
981             this.ipProvisioningTimedOut = ipProvisioningTimedOut;
982             this.role = role;
983         }
984 
985         @Override
equals(Object that)986         public boolean equals(Object that) {
987             if (this == that) return true;
988             if (!(that instanceof ClientModeManagerState)) return false;
989             ClientModeManagerState thatCmmState = (ClientModeManagerState) that;
990             return Objects.equals(ifaceName, thatCmmState.ifaceName)
991                     && connected == thatCmmState.connected
992                     && disconnected == thatCmmState.disconnected
993                     && role == thatCmmState.role
994                     // Since wifiinfo does not have equals currently.
995                     && Objects.equals(wifiInfo.getSSID(), thatCmmState.wifiInfo.getSSID())
996                     && Objects.equals(wifiInfo.getBSSID(), thatCmmState.wifiInfo.getBSSID());
997         }
998 
999         @Override
hashCode()1000         public int hashCode() {
1001             return Objects.hash(ifaceName, connected, disconnected,
1002                     wifiInfo.getSSID(), wifiInfo.getBSSID(), role);
1003         }
1004 
1005         @Override
toString()1006         public String toString() {
1007             return "ClientModeManagerState: " + ifaceName
1008                     + ", role:" + role
1009                     + ", connection state: "
1010                     + (connected ? " connected" : (disconnected ? " disconnected" : "unknown"))
1011                     + ", WifiInfo: " + wifiInfo;
1012         }
1013     }
1014 
1015     /**
1016      * Sets the screen state.
1017      */
setScreenState(boolean screenOn)1018     public void setScreenState(boolean screenOn) {
1019         mScreenOn = screenOn;
1020     }
1021 
1022     /**
1023      * Sets the associated network selection override.
1024      */
setAssociatedNetworkSelectionOverride( @ssociatedNetworkSelectionOverride int value)1025     public boolean setAssociatedNetworkSelectionOverride(
1026             @AssociatedNetworkSelectionOverride int value) {
1027         if (value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE
1028                 && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED
1029                 && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_DISABLED) {
1030             localLog("Error in setting associated network selection override. Invalid value="
1031                     + value);
1032             return false;
1033         }
1034         mAssociatedNetworkSelectionOverride = value;
1035         return true;
1036     }
1037 
1038     /**
1039      * Get whether sufficiency check is enabled based on the current screen state.
1040      */
isSufficiencyCheckEnabled()1041     public boolean isSufficiencyCheckEnabled() {
1042         return mScreenOn ? mSufficiencyCheckEnabledWhenScreenOn
1043                 : mSufficiencyCheckEnabledWhenScreenOff;
1044     }
1045 
1046     /**
1047      * Enable or disable sufficiency check.
1048      */
setSufficiencyCheckEnabled(boolean enabledWhileScreenOff, boolean enabledWhileScreenOn)1049     public void setSufficiencyCheckEnabled(boolean enabledWhileScreenOff,
1050             boolean enabledWhileScreenOn) {
1051         mSufficiencyCheckEnabledWhenScreenOff = enabledWhileScreenOff;
1052         mSufficiencyCheckEnabledWhenScreenOn = enabledWhileScreenOn;
1053     }
1054 
1055     /**
1056      * Enable or disable candidate override with user connect choice.
1057      */
setUserConnectChoiceOverrideEnabled(boolean enabled)1058     public void setUserConnectChoiceOverrideEnabled(boolean enabled) {
1059         mUserConnectChoiceOverrideEnabled = enabled;
1060     }
1061 
1062     /**
1063      * Enable or disable last selection weight.
1064      */
setLastSelectionWeightEnabled(boolean enabled)1065     public void setLastSelectionWeightEnabled(boolean enabled) {
1066         mLastSelectionWeightEnabled = enabled;
1067     }
1068 
getConnectChoiceKey(@onNull List<ClientModeManagerState> cmmStates)1069     private String getConnectChoiceKey(@NonNull List<ClientModeManagerState> cmmStates) {
1070         for (ClientModeManagerState cmmState : cmmStates) {
1071             if (cmmState.role != ROLE_CLIENT_PRIMARY) {
1072                 continue;
1073             }
1074             WifiConfiguration currentNetwork =
1075                     mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId());
1076             if (currentNetwork != null) {
1077                 return currentNetwork.getNetworkSelectionStatus().getConnectChoice();
1078             }
1079         }
1080         return null;
1081     }
1082 
1083     /**
1084      * Returns the list of Candidates from networks in range.
1085      *
1086      * @param scanDetails              List of ScanDetail for all the APs in range
1087      * @param bssidBlocklist           Blocked BSSIDs
1088      * @param cmmStates                State of all long lived client mode manager instances -
1089      *                                 {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} &
1090      *                                 {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
1091      * @param untrustedNetworkAllowed  True if untrusted networks are allowed for connection
1092      * @param oemPaidNetworkAllowed    True if oem paid networks are allowed for connection
1093      * @param oemPrivateNetworkAllowed True if oem private networks are allowed for connection
1094      * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network
1095      * @param skipSufficiencyCheck     True to skip network sufficiency check
1096      * @return list of valid Candidate(s)
1097      */
getCandidatesFromScan( @onNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist, @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, Set<Integer> restrictedNetworkAllowedUids, boolean skipSufficiencyCheck, int autojoinRestrictionSecurityTypes)1098     public List<WifiCandidates.Candidate> getCandidatesFromScan(
1099             @NonNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist,
1100             @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed,
1101             boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed,
1102             Set<Integer> restrictedNetworkAllowedUids, boolean skipSufficiencyCheck,
1103             int autojoinRestrictionSecurityTypes) {
1104         mFilteredNetworks.clear();
1105         mConnectableNetworks.clear();
1106         if (scanDetails.size() == 0) {
1107             localLog("Empty connectivity scan result");
1108             return null;
1109         }
1110 
1111         // Update the scan detail cache at the start, even if we skip network selection
1112         updateScanDetailCache(scanDetails);
1113 
1114         // Update the registered network nominators.
1115         for (NetworkNominator registeredNominator : mNominators) {
1116             registeredNominator.update(scanDetails);
1117         }
1118 
1119         // Filter out unwanted networks.
1120         mFilteredNetworks = filterScanResults(scanDetails, bssidBlocklist, cmmStates,
1121                 autojoinRestrictionSecurityTypes);
1122         if (mFilteredNetworks.size() == 0) {
1123             return null;
1124         }
1125         // Update the matching profiles into WifiConfigManager, help displaying Passpoint networks
1126         // in Wifi Picker
1127         mWifiInjector.getPasspointNetworkNominateHelper().updatePasspointConfig(mFilteredNetworks);
1128 
1129         boolean networkSelectionNeeded = skipSufficiencyCheck
1130                 || isNetworkSelectionNeeded(cmmStates);
1131         final String userConnectChoiceKey;
1132         if (!networkSelectionNeeded) {
1133             if (!isAssociatedNetworkSelectionEnabled()) {
1134                 // Skip network selection based on connect choice because associated network
1135                 // selection is disabled.
1136                 return null;
1137             }
1138             userConnectChoiceKey = getConnectChoiceKey(cmmStates);
1139             if (userConnectChoiceKey == null) {
1140                 return null;
1141             }
1142             // Continue candidate selection but only allow the user connect choice as candidate
1143             localLog("Current network is sufficient. Continue network selection only "
1144                     + "considering user connect choice: " + userConnectChoiceKey);
1145         } else {
1146             userConnectChoiceKey = null;
1147         }
1148 
1149         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
1150         if (userConnectChoiceKey == null) {
1151             // Add connected network as candidates unless only considering connect choice.
1152             for (ClientModeManagerState cmmState : cmmStates) {
1153                 // Always get the current BSSID from WifiInfo in case that firmware initiated
1154                 // roaming happened.
1155                 String currentBssid = cmmState.wifiInfo.getBSSID();
1156                 WifiConfiguration currentNetwork =
1157                         mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId());
1158                 if (currentNetwork != null && currentBssid != null) {
1159                     wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid);
1160                     // We always want the current network to be a candidate so that it can
1161                     // participate.
1162                     // It may also get re-added by a nominator, in which case this fallback
1163                     // will be replaced.
1164                     MacAddress bssid = MacAddress.fromString(currentBssid);
1165                     SecurityParams params = currentNetwork.getNetworkSelectionStatus()
1166                             .getLastUsedSecurityParams();
1167                     if (null == params) {
1168                         localLog("No known candidate security params for current network.");
1169                         continue;
1170                     }
1171                     WifiCandidates.Key key = new WifiCandidates.Key(
1172                             ScanResultMatchInfo.fromWifiConfiguration(currentNetwork),
1173                             bssid, currentNetwork.networkId,
1174                             params.getSecurityType());
1175                     ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid);
1176                     int predictedTputMbps = (scanDetail == null) ? 0
1177                             : predictThroughput(scanDetail);
1178                     wifiCandidates.add(key, currentNetwork,
1179                             NetworkNominator.NOMINATOR_ID_CURRENT,
1180                             cmmState.wifiInfo.getRssi(),
1181                             cmmState.wifiInfo.getFrequency(),
1182                             ScanResult.CHANNEL_WIDTH_20MHZ, // channel width unavailable in WifiInfo
1183                             calculateLastSelectionWeight(currentNetwork.networkId,
1184                                     WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo)),
1185                             WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo),
1186                             isFromCarrierOrPrivilegedApp(currentNetwork),
1187                             predictedTputMbps,
1188                             (scanDetail != null) ? scanDetail.getScanResult().getApMldMacAddress()
1189                                     : null);
1190                 }
1191             }
1192         }
1193 
1194         // Update all configured networks before initiating network selection.
1195         updateConfiguredNetworks();
1196 
1197         List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates = mWifiInjector
1198                 .getPasspointNetworkNominateHelper()
1199                 .getPasspointNetworkCandidates(new ArrayList<>(mFilteredNetworks));
1200         for (NetworkNominator registeredNominator : mNominators) {
1201             localLog("About to run " + registeredNominator.getName() + " :");
1202             registeredNominator.nominateNetworks(
1203                     new ArrayList<>(mFilteredNetworks), passpointCandidates,
1204                     untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed,
1205                     restrictedNetworkAllowedUids, (scanDetail, config) -> {
1206                         WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
1207                                 scanDetail, config);
1208                         if (key != null) {
1209                             if (userConnectChoiceKey != null
1210                                     && !userConnectChoiceKey.equals(config.getProfileKey())) {
1211                                 return;
1212                             }
1213                             boolean metered = false;
1214                             for (ClientModeManagerState cmmState : cmmStates) {
1215                                 if (isEverMetered(config, cmmState.wifiInfo, scanDetail)) {
1216                                     metered = true;
1217                                     break;
1218                                 }
1219                             }
1220                             // TODO(b/151981920) Saved passpoint candidates are marked ephemeral
1221                             boolean added = wifiCandidates.add(key, config,
1222                                     registeredNominator.getId(),
1223                                     scanDetail.getScanResult().level,
1224                                     scanDetail.getScanResult().frequency,
1225                                     scanDetail.getScanResult().channelWidth,
1226                                     calculateLastSelectionWeight(config.networkId, metered),
1227                                     metered,
1228                                     isFromCarrierOrPrivilegedApp(config),
1229                                     predictThroughput(scanDetail),
1230                                     scanDetail.getScanResult().getApMldMacAddress());
1231                             if (added) {
1232                                 mConnectableNetworks.add(Pair.create(scanDetail, config));
1233                                 mWifiConfigManager.updateScanDetailForNetwork(
1234                                         config.networkId, scanDetail);
1235                                 mWifiMetrics.setNominatorForNetwork(config.networkId,
1236                                         toProtoNominatorId(registeredNominator.getId()));
1237                             }
1238                         }
1239                     });
1240         }
1241         if (mConnectableNetworks.size() != wifiCandidates.size()) {
1242             localLog("Connectable: " + mConnectableNetworks.size()
1243                     + " Candidates: " + wifiCandidates.size());
1244         }
1245 
1246         // Update multi link candidate throughput before network selection.
1247         updateMultiLinkCandidatesThroughput(wifiCandidates);
1248 
1249         return wifiCandidates.getCandidates();
1250     }
1251 
1252     /**
1253      * Check Wi-Fi7 is enabled for all candidates.
1254      */
isWifi7Enabled(List<WifiCandidates.Candidate> candidates)1255     private boolean isWifi7Enabled(List<WifiCandidates.Candidate> candidates) {
1256         for (WifiCandidates.Candidate candidate : candidates) {
1257             if (!mWifiConfigManager.isWifi7Enabled(candidate.getNetworkConfigId())) return false;
1258         }
1259         return true;
1260     }
1261 
1262     /**
1263      * Update multi link candidate's throughput which is used in network selection by
1264      * {@link ThroughputScorer}
1265      *
1266      * Algorithm:
1267      * {@link WifiNative#getSupportedBandCombinations(String)} returns a list of band combinations
1268      * supported by the chip. e.g. { {2.4}, {5}, {6}, {2.4, 5}, {2.4, 6}, {5, 6} }.
1269      *
1270      * During the creation of candidate list, members which have same MLD AP MAC address are grouped
1271      * together. Let's say we have the following multi link candidates in one group {C_2.4, C_5,
1272      * C_6}. First intersect this list with allowed combination to get a collection like this,
1273      * { {C_2.4}, {C_5}, {C_6}, {C_2.4, C_5}, {C_2.4, C_6}, {C_5, C_6} }. For each of the sub-group,
1274      * predicted single link throughputs are added and each candidate in the subgroup get an
1275      * updated multi link throughput if the saved value is less. This calculation takes care of
1276      * eMLSR and STR.
1277      *
1278      * If the chip can't support all the radios for multi-link operation at the same time for STR
1279      * operation, we can't use the higher-order radio combinations.
1280      *
1281      * Above algorithm is extendable to multiple links with any number of bands and link
1282      * restriction.
1283      *
1284      * @param wifiCandidates A list of WifiCandidates
1285      */
updateMultiLinkCandidatesThroughput(WifiCandidates wifiCandidates)1286     private void updateMultiLinkCandidatesThroughput(WifiCandidates wifiCandidates) {
1287         ClientModeManager primaryManager =
1288                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
1289         if (primaryManager == null) return;
1290         String interfaceName = primaryManager.getInterfaceName();
1291         if (interfaceName == null) return;
1292 
1293         // Check if the chip has more than one MLO STR link support.
1294         int maxMloStrLinkCount = mWifiNative.getMaxMloStrLinkCount(interfaceName);
1295         if (maxMloStrLinkCount <= 1) return;
1296 
1297         Set<List<Integer>> simultaneousBandCombinations = mWifiNative.getSupportedBandCombinations(
1298                 interfaceName);
1299         if (simultaneousBandCombinations == null) return;
1300 
1301         for (List<WifiCandidates.Candidate> mlCandidates :
1302                 wifiCandidates.getMultiLinkCandidates()) {
1303             if (!isWifi7Enabled(mlCandidates)) continue;
1304             for (List<Integer> bands : simultaneousBandCombinations) {
1305                 // Limit the radios/bands to maximum STR link supported in multi link operation.
1306                 if (bands.size() > maxMloStrLinkCount) break;
1307                 List<Integer> strBandsToIntersect = new ArrayList<>(bands);
1308                 List<WifiCandidates.Candidate> strMlCandidates = intersectMlCandidatesWithStrBands(
1309                         mlCandidates, strBandsToIntersect);
1310                 if (strMlCandidates != null) {
1311                     aggregateStrMultiLinkThroughput(strMlCandidates);
1312                 }
1313             }
1314         }
1315     }
1316 
1317     /**
1318      * Return the intersection of STR band combinations and best Multi-Link Wi-Fi candidates.
1319      */
intersectMlCandidatesWithStrBands( @onNull List<WifiCandidates.Candidate> candidates, @NonNull List<Integer> bands)1320     private List<WifiCandidates.Candidate> intersectMlCandidatesWithStrBands(
1321             @NonNull List<WifiCandidates.Candidate> candidates, @NonNull List<Integer> bands) {
1322         // Sorting is needed here to make the best candidates first in the list.
1323         List<WifiCandidates.Candidate> intersectedCandidates = candidates.stream()
1324                 .sorted(Comparator.comparingInt(
1325                         WifiCandidates.Candidate::getPredictedThroughputMbps).reversed())
1326                 .filter(k -> {
1327                     int band = Integer.valueOf(ScanResult.toBand(k.getFrequency()));
1328                     if (bands.contains(band)) {
1329                         // Remove first occurrence as it is counted already.
1330                         bands.remove(bands.indexOf(band));
1331                         return true;
1332                     }
1333                     return false;
1334                 })
1335                 .collect(Collectors.toList());
1336         // Make sure all bands are intersected.
1337         return (bands.isEmpty()) ? intersectedCandidates : null;
1338     }
1339 
1340     /**
1341      * Aggregate the throughput of STR multi-link candidates.
1342      */
aggregateStrMultiLinkThroughput( @onNull List<WifiCandidates.Candidate> candidates)1343     private void aggregateStrMultiLinkThroughput(
1344             @NonNull List<WifiCandidates.Candidate> candidates) {
1345         // Add all throughputs.
1346         int predictedMlThroughput = candidates.stream()
1347                 .mapToInt(c -> c.getPredictedThroughputMbps())
1348                 .sum();
1349         // Check if an update needed for multi link throughput.
1350         candidates.stream()
1351                 .filter(c -> c.getPredictedMultiLinkThroughputMbps() < predictedMlThroughput)
1352                 .forEach(c -> c.setPredictedMultiLinkThroughputMbps(predictedMlThroughput));
1353     }
1354 
1355     /**
1356      * Add all results as candidates for the user selected network and let network selection
1357      * chooses the proper one for the user selected network.
1358      * @param config                  The configuration for the user selected network.
1359      * @param scanDetails              List of ScanDetail for the user selected network.
1360      * @return list of valid Candidate(s)
1361      */
getCandidatesForUserSelection( WifiConfiguration config, @NonNull List<ScanDetail> scanDetails)1362     public List<WifiCandidates.Candidate> getCandidatesForUserSelection(
1363             WifiConfiguration config, @NonNull List<ScanDetail> scanDetails) {
1364         if (scanDetails.size() == 0) {
1365             if (mVerboseLoggingEnabled) {
1366                 Log.d(TAG, "No scan result for the user selected network.");
1367                 return null;
1368             }
1369         }
1370 
1371         mConnectableNetworks.clear();
1372         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
1373         for (ScanDetail scanDetail: scanDetails) {
1374             WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
1375                     scanDetail, config);
1376             if (null == key) continue;
1377 
1378             boolean added = wifiCandidates.add(key, config,
1379                     WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_CURRENT,
1380                     scanDetail.getScanResult().level,
1381                     scanDetail.getScanResult().frequency,
1382                     scanDetail.getScanResult().channelWidth,
1383                     0.0 /* lastSelectionWeightBetweenZeroAndOne */,
1384                     false /* isMetered */,
1385                     WifiNetworkSelector.isFromCarrierOrPrivilegedApp(config),
1386                     predictThroughput(scanDetail), scanDetail.getScanResult().getApMldMacAddress());
1387             if (!added) continue;
1388 
1389             mConnectableNetworks.add(Pair.create(scanDetail, config));
1390             mWifiConfigManager.updateScanDetailForNetwork(
1391                     config.networkId, scanDetail);
1392         }
1393         return wifiCandidates.getCandidates();
1394     }
1395 
1396     /**
1397      * For transition networks with only legacy networks,
1398      * remove auto-upgrade type to use the legacy type to
1399      * avoid roaming issues between two types.
1400      */
removeAutoUpgradeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList, @WifiConfiguration.SecurityType int baseSecurityType, @WifiConfiguration.SecurityType int upgradableSecurityType, boolean isLegacyNetworkInRange, boolean isUpgradableTypeOnlyInRange, boolean isAutoUpgradeEnabled)1401     private void removeAutoUpgradeSecurityParamsIfNecessary(
1402             WifiConfiguration config,
1403             List<SecurityParams> scanResultParamsList,
1404             @WifiConfiguration.SecurityType int baseSecurityType,
1405             @WifiConfiguration.SecurityType int upgradableSecurityType,
1406             boolean isLegacyNetworkInRange,
1407             boolean isUpgradableTypeOnlyInRange,
1408             boolean isAutoUpgradeEnabled) {
1409         localLog("removeAutoUpgradeSecurityParamsIfNecessary:"
1410                 + " SSID: " + config.SSID
1411                 + " baseSecurityType: " + baseSecurityType
1412                 + " upgradableSecurityType: " + upgradableSecurityType
1413                 + " isLegacyNetworkInRange: " + isLegacyNetworkInRange
1414                 + " isUpgradableTypeOnlyInRange: " + isUpgradableTypeOnlyInRange
1415                 + " isAutoUpgradeEnabled: " + isAutoUpgradeEnabled);
1416         if (isAutoUpgradeEnabled) {
1417             // Consider removing the auto-upgraded type if legacy networks are in range.
1418             if (!isLegacyNetworkInRange) return;
1419             // If base params is disabled or removed, keep the auto-upgrade params.
1420             SecurityParams baseParams = config.getSecurityParams(baseSecurityType);
1421             if (null == baseParams || !baseParams.isEnabled()) return;
1422             // If there are APs with standalone-upgradeable security type is in range,
1423             // do not consider removing the auto-upgraded type.
1424             if (isUpgradableTypeOnlyInRange) return;
1425         }
1426 
1427         SecurityParams upgradableParams = config.getSecurityParams(upgradableSecurityType);
1428         if (null == upgradableParams) return;
1429         if (!upgradableParams.isAddedByAutoUpgrade()) return;
1430         localLog("Remove upgradable security type " + upgradableSecurityType + " for the network.");
1431         scanResultParamsList.removeIf(p -> p.isSecurityType(upgradableSecurityType));
1432     }
1433 
1434     /** Helper function to place all conditions which need to remove auto-upgrade types. */
removeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList)1435     private void removeSecurityParamsIfNecessary(
1436             WifiConfiguration config,
1437             List<SecurityParams> scanResultParamsList) {
1438         // When offload is supported, both types are passed down.
1439         if (!mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) {
1440             removeAutoUpgradeSecurityParamsIfNecessary(
1441                     config, scanResultParamsList,
1442                     WifiConfiguration.SECURITY_TYPE_PSK,
1443                     WifiConfiguration.SECURITY_TYPE_SAE,
1444                     mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(config.SSID),
1445                     mScanRequestProxy.isWpa3PersonalOnlyNetworkInRange(config.SSID),
1446                     mWifiGlobals.isWpa3SaeUpgradeEnabled());
1447         }
1448         removeAutoUpgradeSecurityParamsIfNecessary(
1449                 config, scanResultParamsList,
1450                 WifiConfiguration.SECURITY_TYPE_OPEN,
1451                 WifiConfiguration.SECURITY_TYPE_OWE,
1452                 mScanRequestProxy.isOpenOnlyNetworkInRange(config.SSID),
1453                 mScanRequestProxy.isOweOnlyNetworkInRange(config.SSID),
1454                 mWifiGlobals.isOweUpgradeEnabled());
1455         removeAutoUpgradeSecurityParamsIfNecessary(
1456                 config, scanResultParamsList,
1457                 WifiConfiguration.SECURITY_TYPE_EAP,
1458                 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
1459                 mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(config.SSID),
1460                 mScanRequestProxy.isWpa3EnterpriseOnlyNetworkInRange(config.SSID),
1461                 true);
1462         // When using WPA3 (SAE), all passwords in all lengths are strings, but when using WPA2,
1463         // there is a distinction between 8-63 octets that go through BDKDF2 function, and
1464         // 64-octets that are assumed to be the output of it. BDKDF2 is not applicable to SAE
1465         // and to prevent interop issues with APs when 64-octet Hex PSK is configured, update
1466         // the configuration to use WPA2 only.
1467         WifiConfiguration configWithPassword = mWifiConfigManager
1468                 .getConfiguredNetworkWithPassword(config.networkId);
1469         if (configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
1470                 && configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
1471                 && configWithPassword.preSharedKey != null
1472                 && !configWithPassword.preSharedKey.startsWith("\"")
1473                 && configWithPassword.preSharedKey.length() == 64
1474                 && configWithPassword.preSharedKey.matches("[0-9A-Fa-f]{64}")) {
1475             localLog("Remove SAE type for " + configWithPassword.SSID + " with 64-octet Hex PSK.");
1476             scanResultParamsList
1477                     .removeIf(p -> p.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE));
1478         }
1479     }
1480 
1481     /**
1482      * For the transition mode, MFPC should be true, and MFPR should be false,
1483      * see WPA3 SAE specification section 2.3 and 3.3.
1484      */
updateSecurityParamsForTransitionModeIfNecessary( ScanResult scanResult, SecurityParams params)1485     private void updateSecurityParamsForTransitionModeIfNecessary(
1486             ScanResult scanResult, SecurityParams params) {
1487         if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
1488                 && params.isAddedByAutoUpgrade()
1489                 && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult)) {
1490             params.setRequirePmf(false);
1491         } else if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)
1492                 && params.isAddedByAutoUpgrade()
1493                 && ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
1494             params.setRequirePmf(false);
1495         }
1496     }
1497 
1498     /**
1499      * Update the candidate security params against a scan detail.
1500      *
1501      * @param network the target network.
1502      * @param scanDetail the target scan detail.
1503      */
updateNetworkCandidateSecurityParams( WifiConfiguration network, ScanDetail scanDetail)1504     private void updateNetworkCandidateSecurityParams(
1505             WifiConfiguration network, ScanDetail scanDetail) {
1506         if (network == null) return;
1507         if (scanDetail == null) return;
1508 
1509         ScanResult scanResult = scanDetail.getScanResult();
1510         List<SecurityParams> scanResultParamsList = ScanResultUtil
1511                 .generateSecurityParamsListFromScanResult(scanResult);
1512         if (scanResultParamsList == null) return;
1513         // Under some conditions, the legacy type is preferred to have better
1514         // connectivity behaviors, and the auto-upgrade type should be removed.
1515         removeSecurityParamsIfNecessary(network, scanResultParamsList);
1516         SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
1517                 network,
1518                 scanResultParamsList);
1519         if (params == null) return;
1520         updateSecurityParamsForTransitionModeIfNecessary(scanResult, params);
1521         mWifiConfigManager.setNetworkCandidateScanResult(
1522                 network.networkId, scanResult, 0, params);
1523     }
1524 
1525     /**
1526      * Using the registered Scorers, choose the best network from the list of Candidate(s).
1527      * The ScanDetailCache is also updated here.
1528      * @param candidates - Candidates to perferm network selection on.
1529      * @return WifiConfiguration - the selected network, or null.
1530      */
1531     @Nullable
selectNetwork(@onNull List<WifiCandidates.Candidate> candidates)1532     public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates) {
1533         return selectNetwork(candidates, true);
1534     }
1535 
1536     /**
1537      * Using the registered Scorers, choose the best network from the list of Candidate(s).
1538      * The ScanDetailCache is also updated here.
1539      * @param candidates - Candidates to perform network selection on.
1540      * @param overrideEnabled If it is allowed to override candidate with User Connect Choice.
1541      * @return WifiConfiguration - the selected network, or null.
1542      */
1543     @Nullable
selectNetwork(@onNull List<WifiCandidates.Candidate> candidates, boolean overrideEnabled)1544     public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates,
1545             boolean overrideEnabled) {
1546         if (candidates == null || candidates.size() == 0) {
1547             return null;
1548         }
1549         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext, candidates);
1550         final WifiCandidates.CandidateScorer activeScorer = getActiveCandidateScorer();
1551         // Update the NetworkSelectionStatus in the configs for the current candidates
1552         // This is needed for the legacy user connect choice, at least
1553         Collection<Collection<WifiCandidates.Candidate>> groupedCandidates =
1554                 wifiCandidates.getGroupedCandidates();
1555         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
1556             WifiCandidates.ScoredCandidate choice = activeScorer.scoreCandidates(group);
1557             if (choice == null) continue;
1558             ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey);
1559             if (scanDetail == null) continue;
1560             WifiConfiguration config = mWifiConfigManager
1561                     .getConfiguredNetwork(choice.candidateKey.networkId);
1562             if (config == null) continue;
1563             updateNetworkCandidateSecurityParams(config, scanDetail);
1564         }
1565 
1566         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
1567             for (WifiCandidates.Candidate candidate : group.stream()
1568                     .sorted((a, b) -> (b.getScanRssi() - a.getScanRssi())) // decreasing rssi
1569                     .collect(Collectors.toList())) {
1570                 localLog(candidate.toString());
1571             }
1572         }
1573 
1574         ArrayMap<Integer, Integer> experimentNetworkSelections = new ArrayMap<>(); // for metrics
1575 
1576         int selectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
1577 
1578         // Run all the CandidateScorers
1579         boolean legacyOverrideWanted = true;
1580         for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
1581             WifiCandidates.ScoredCandidate choice;
1582             try {
1583                 choice = wifiCandidates.choose(candidateScorer);
1584             } catch (RuntimeException e) {
1585                 Log.wtf(TAG, "Exception running a CandidateScorer", e);
1586                 continue;
1587             }
1588             int networkId = choice.candidateKey == null
1589                     ? WifiConfiguration.INVALID_NETWORK_ID
1590                     : choice.candidateKey.networkId;
1591             String chooses = " would choose ";
1592             if (candidateScorer == activeScorer) {
1593                 chooses = " chooses ";
1594                 legacyOverrideWanted = choice.userConnectChoiceOverride;
1595                 selectedNetworkId = networkId;
1596                 updateChosenPasspointNetwork(choice);
1597             }
1598             String id = candidateScorer.getIdentifier();
1599             int expid = experimentIdFromIdentifier(id);
1600             localLog(id + chooses + networkId
1601                     + " score " + choice.value + "+/-" + choice.err
1602                     + " expid " + expid);
1603             experimentNetworkSelections.put(expid, networkId);
1604         }
1605 
1606         // Update metrics about differences in the selections made by various methods
1607         final int activeExperimentId = experimentIdFromIdentifier(activeScorer.getIdentifier());
1608         for (Map.Entry<Integer, Integer> entry :
1609                 experimentNetworkSelections.entrySet()) {
1610             int experimentId = entry.getKey();
1611             if (experimentId == activeExperimentId) continue;
1612             int thisSelectedNetworkId = entry.getValue();
1613             mWifiMetrics.logNetworkSelectionDecision(experimentId, activeExperimentId,
1614                     selectedNetworkId == thisSelectedNetworkId,
1615                     groupedCandidates.size());
1616         }
1617 
1618         // Get a fresh copy of WifiConfiguration reflecting any scan result updates
1619         WifiConfiguration selectedNetwork =
1620                 mWifiConfigManager.getConfiguredNetwork(selectedNetworkId);
1621         if (selectedNetwork != null && legacyOverrideWanted && overrideEnabled
1622                 && mUserConnectChoiceOverrideEnabled) {
1623             selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork);
1624         }
1625         if (selectedNetwork != null) {
1626             mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
1627         }
1628         return selectedNetwork;
1629     }
1630 
1631     /**
1632      * Returns the ScanDetail given the candidate key, using the saved list of connectible networks.
1633      */
getScanDetailForCandidateKey(WifiCandidates.Key candidateKey)1634     private ScanDetail getScanDetailForCandidateKey(WifiCandidates.Key candidateKey) {
1635         if (candidateKey == null) return null;
1636         String bssid = candidateKey.bssid.toString();
1637         for (Pair<ScanDetail, WifiConfiguration> pair : mConnectableNetworks) {
1638             if (candidateKey.networkId == pair.second.networkId
1639                     && bssid.equals(pair.first.getBSSIDString())) {
1640                 return pair.first;
1641             }
1642         }
1643         return null;
1644     }
1645 
updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice)1646     private void updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice) {
1647         if (choice.candidateKey == null) {
1648             return;
1649         }
1650         WifiConfiguration config =
1651                 mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId);
1652         if (config == null) {
1653             return;
1654         }
1655         if (config.isPasspoint()) {
1656             config.SSID = choice.candidateKey.matchInfo.networkSsid;
1657             mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName,
1658                     false);
1659         }
1660     }
1661 
updateScanDetailCache(List<ScanDetail> scanDetails)1662     private void updateScanDetailCache(List<ScanDetail> scanDetails) {
1663         for (ScanDetail scanDetail : scanDetails) {
1664             mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
1665         }
1666     }
1667 
toProtoNominatorId(@etworkNominator.NominatorId int nominatorId)1668     private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) {
1669         switch (nominatorId) {
1670             case NetworkNominator.NOMINATOR_ID_SAVED:
1671                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED;
1672             case NetworkNominator.NOMINATOR_ID_SUGGESTION:
1673                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SUGGESTION;
1674             case NetworkNominator.NOMINATOR_ID_SCORED:
1675                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_EXTERNAL_SCORED;
1676             case NetworkNominator.NOMINATOR_ID_CURRENT:
1677                 Log.e(TAG, "Unexpected NOMINATOR_ID_CURRENT", new RuntimeException());
1678                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
1679             default:
1680                 Log.e(TAG, "UnrecognizedNominatorId" + nominatorId);
1681                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
1682         }
1683     }
1684 
calculateLastSelectionWeight(int networkId, boolean isMetered)1685     private double calculateLastSelectionWeight(int networkId, boolean isMetered) {
1686         if (!mLastSelectionWeightEnabled
1687                 || networkId != mWifiConfigManager.getLastSelectedNetwork()) {
1688             return 0.0;
1689         }
1690         double timeDifference = mClock.getElapsedSinceBootMillis()
1691                 - mWifiConfigManager.getLastSelectedTimeStamp();
1692         long millis = TimeUnit.MINUTES.toMillis(isMetered
1693                 ? mScoringParams.getLastMeteredSelectionMinutes()
1694                 : mScoringParams.getLastUnmeteredSelectionMinutes());
1695         if (timeDifference >= millis) return 0.0;
1696         double unclipped = 1.0 - (timeDifference / millis);
1697         return Math.min(Math.max(unclipped, 0.0), 1.0);
1698     }
1699 
getActiveCandidateScorer()1700     private WifiCandidates.CandidateScorer getActiveCandidateScorer() {
1701         WifiCandidates.CandidateScorer ans = mCandidateScorers.get(PRESET_CANDIDATE_SCORER_NAME);
1702         int overrideExperimentId = mScoringParams.getExperimentIdentifier();
1703         if (overrideExperimentId >= MIN_SCORER_EXP_ID) {
1704             for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
1705                 int expId = experimentIdFromIdentifier(candidateScorer.getIdentifier());
1706                 if (expId == overrideExperimentId) {
1707                     ans = candidateScorer;
1708                     break;
1709                 }
1710             }
1711         }
1712         if (ans == null && PRESET_CANDIDATE_SCORER_NAME != null) {
1713             Log.wtf(TAG, PRESET_CANDIDATE_SCORER_NAME + " is not registered!");
1714         }
1715         mWifiMetrics.setNetworkSelectorExperimentId(ans == null
1716                 ? LEGACY_CANDIDATE_SCORER_EXP_ID
1717                 : experimentIdFromIdentifier(ans.getIdentifier()));
1718         return ans;
1719     }
1720 
predictThroughput(@onNull ScanDetail scanDetail)1721     private int predictThroughput(@NonNull ScanDetail scanDetail) {
1722         if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) {
1723             return 0;
1724         }
1725         int channelUtilizationLinkLayerStats = BssLoad.INVALID;
1726         if (mWifiChannelUtilization != null) {
1727             channelUtilizationLinkLayerStats =
1728                     mWifiChannelUtilization.getUtilizationRatio(
1729                             scanDetail.getScanResult().frequency);
1730         }
1731         ClientModeManager primaryManager =
1732                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
1733         return mThroughputPredictor.predictThroughput(
1734                 primaryManager.getDeviceWiphyCapabilities(),
1735                 scanDetail.getScanResult().getWifiStandard(),
1736                 scanDetail.getScanResult().channelWidth,
1737                 scanDetail.getScanResult().level,
1738                 scanDetail.getScanResult().frequency,
1739                 scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
1740                 scanDetail.getNetworkDetail().getChannelUtilization(),
1741                 channelUtilizationLinkLayerStats,
1742                 mWifiGlobals.isBluetoothConnected(),
1743                 scanDetail.getNetworkDetail().getDisabledSubchannelBitmap());
1744     }
1745 
1746     /**
1747      * Register a network nominator
1748      *
1749      * @param nominator the network nominator to be registered
1750      */
registerNetworkNominator(@onNull NetworkNominator nominator)1751     public void registerNetworkNominator(@NonNull NetworkNominator nominator) {
1752         mNominators.add(Preconditions.checkNotNull(nominator));
1753     }
1754 
1755     /**
1756      * Register a candidate scorer.
1757      *
1758      * Replaces any existing scorer having the same identifier.
1759      */
registerCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1760     public void registerCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1761         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1762         if (name != null) {
1763             mCandidateScorers.put(name, candidateScorer);
1764         }
1765     }
1766 
1767     /**
1768      * Unregister a candidate scorer.
1769      */
unregisterCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1770     public void unregisterCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1771         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1772         if (name != null) {
1773             mCandidateScorers.remove(name);
1774         }
1775     }
1776 
1777     /**
1778      * Indicate whether or not a configuration is from carrier or privileged app.
1779      *
1780      * @param config The network configuration
1781      * @return true if this configuration is from carrier or privileged app; false otherwise.
1782      */
isFromCarrierOrPrivilegedApp(WifiConfiguration config)1783     public static boolean isFromCarrierOrPrivilegedApp(WifiConfiguration config) {
1784         if (config.fromWifiNetworkSuggestion
1785                 && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
1786             // Privileged carrier suggestion
1787             return true;
1788         }
1789         if (config.isEphemeral()
1790                 && !config.fromWifiNetworkSpecifier
1791                 && !config.fromWifiNetworkSuggestion) {
1792             // From ScoredNetworkNominator
1793             return true;
1794         }
1795         return false;
1796     }
1797 
1798     /**
1799      * Derives a numeric experiment identifier from a CandidateScorer's identifier.
1800      *
1801      * @returns a positive number that starts with the decimal digits ID_PREFIX
1802      */
experimentIdFromIdentifier(String id)1803     public static int experimentIdFromIdentifier(String id) {
1804         final int digits = (int) (((long) id.hashCode()) & Integer.MAX_VALUE) % ID_SUFFIX_MOD;
1805         return ID_PREFIX * ID_SUFFIX_MOD + digits;
1806     }
1807 
1808     private static final int ID_SUFFIX_MOD = 1_000_000;
1809     private static final int ID_PREFIX = 42;
1810     private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD;
1811 
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, WifiNative wifiNative)1812     WifiNetworkSelector(
1813             Context context,
1814             WifiScoreCard wifiScoreCard,
1815             ScoringParams scoringParams,
1816             WifiConfigManager configManager,
1817             Clock clock,
1818             LocalLog localLog,
1819             WifiMetrics wifiMetrics,
1820             WifiInjector wifiInjector,
1821             ThroughputPredictor throughputPredictor,
1822             WifiChannelUtilization wifiChannelUtilization,
1823             WifiGlobals wifiGlobals,
1824             ScanRequestProxy scanRequestProxy,
1825             WifiNative wifiNative) {
1826         mContext = context;
1827         mWifiScoreCard = wifiScoreCard;
1828         mScoringParams = scoringParams;
1829         mWifiConfigManager = configManager;
1830         mClock = clock;
1831         mLocalLog = localLog;
1832         mWifiMetrics = wifiMetrics;
1833         mWifiInjector = wifiInjector;
1834         mThroughputPredictor = throughputPredictor;
1835         mWifiChannelUtilization = wifiChannelUtilization;
1836         mWifiGlobals = wifiGlobals;
1837         mScanRequestProxy = scanRequestProxy;
1838         mWifiNative = wifiNative;
1839         mDevicePolicyManager = WifiPermissionsUtil.retrieveDevicePolicyManagerFromContext(mContext);
1840     }
1841 }
1842