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