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)469     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
470             Set<String> bssidBlocklist, List<ClientModeManagerState> cmmStates) {
471         List<ScanDetail> validScanDetails = new ArrayList<>();
472         StringBuffer noValidSsid = new StringBuffer();
473         StringBuffer blockedBssid = new StringBuffer();
474         StringBuffer lowRssi = new StringBuffer();
475         StringBuffer mboAssociationDisallowedBssid = new StringBuffer();
476         StringBuffer adminRestrictedSsid = new StringBuffer();
477         StringJoiner deprecatedSecurityTypeSsid = new StringJoiner(" / ");
478         List<String> currentBssids = cmmStates.stream()
479                 .map(cmmState -> cmmState.wifiInfo.getBSSID())
480                 .collect(Collectors.toList());
481         Set<String> scanResultPresentForCurrentBssids = new ArraySet<>();
482 
483         int adminMinimumSecurityLevel = 0;
484         boolean adminSsidRestrictionSet = false;
485         Set<WifiSsid> adminSsidAllowlist = new ArraySet<>();
486         Set<WifiSsid> admindSsidDenylist = new ArraySet<>();
487 
488         int numBssidFiltered = 0;
489 
490         if (mDevicePolicyManager != null && SdkLevel.isAtLeastT()) {
491             adminMinimumSecurityLevel =
492                     mDevicePolicyManager.getMinimumRequiredWifiSecurityLevel();
493             WifiSsidPolicy policy = mDevicePolicyManager.getWifiSsidPolicy();
494             if (policy != null) {
495                 adminSsidRestrictionSet = true;
496                 if (policy.getPolicyType() == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
497                     adminSsidAllowlist = policy.getSsids();
498                 } else {
499                     admindSsidDenylist = policy.getSsids();
500                 }
501             }
502         }
503 
504         for (ScanDetail scanDetail : scanDetails) {
505             ScanResult scanResult = scanDetail.getScanResult();
506 
507             if (TextUtils.isEmpty(scanResult.SSID)) {
508                 noValidSsid.append(scanResult.BSSID).append(" / ");
509                 continue;
510             }
511 
512             // Check if the scan results contain the currently connected BSSID's
513             if (currentBssids.contains(scanResult.BSSID)) {
514                 scanResultPresentForCurrentBssids.add(scanResult.BSSID);
515                 validScanDetails.add(scanDetail);
516                 continue;
517             }
518 
519             final String scanId = toScanId(scanResult);
520 
521             if (bssidBlocklist.contains(scanResult.BSSID)) {
522                 blockedBssid.append(scanId).append(" / ");
523                 numBssidFiltered++;
524                 continue;
525             }
526 
527             // Skip network with too weak signals.
528             if (isSignalTooWeak(scanResult)) {
529                 lowRssi.append(scanId);
530                 if (scanResult.is24GHz()) {
531                     lowRssi.append("(2.4GHz)");
532                 } else if (scanResult.is5GHz()) {
533                     lowRssi.append("(5GHz)");
534                 } else if (scanResult.is6GHz()) {
535                     lowRssi.append("(6GHz)");
536                 }
537                 lowRssi.append(scanResult.level).append(" / ");
538                 continue;
539             }
540 
541             // Skip BSS which is not accepting new connections.
542             NetworkDetail networkDetail = scanDetail.getNetworkDetail();
543             if (networkDetail != null) {
544                 if (networkDetail.getMboAssociationDisallowedReasonCode()
545                         != MboOceConstants.MBO_OCE_ATTRIBUTE_NOT_PRESENT) {
546                     mWifiMetrics
547                             .incrementNetworkSelectionFilteredBssidCountDueToMboAssocDisallowInd();
548                     mboAssociationDisallowedBssid.append(scanId).append("(")
549                             .append(networkDetail.getMboAssociationDisallowedReasonCode())
550                             .append(")").append(" / ");
551                     continue;
552                 }
553             }
554 
555             // Skip network that does not meet the admin set SSID restriction
556             if (adminSsidRestrictionSet) {
557                 WifiSsid ssid = scanResult.getWifiSsid();
558                 // Allowlist policy set but network is not present in the list
559                 if (!adminSsidAllowlist.isEmpty() && !adminSsidAllowlist.contains(ssid)) {
560                     adminRestrictedSsid.append(scanId).append(" / ");
561                     continue;
562                 }
563                 // Denylist policy set but network is present in the list
564                 if (!admindSsidDenylist.isEmpty() && admindSsidDenylist.contains(ssid)) {
565                     adminRestrictedSsid.append(scanId).append(" / ");
566                     continue;
567                 }
568             }
569 
570             // Skip network that does not meet the admin set minimum security level restriction
571             if (adminMinimumSecurityLevel != 0) {
572                 boolean securityRestrictionPassed = false;
573                 @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes();
574                 for (int type : securityTypes) {
575                     int securityLevel = WifiInfo.convertSecurityTypeToDpmWifiSecurity(type);
576 
577                     // Skip unknown security type since security level cannot be determined.
578                     // If all the security types are unknown when the minimum security level
579                     // restriction is set, the scan result is ignored.
580                     if (securityLevel == WifiInfo.DPM_SECURITY_TYPE_UNKNOWN) continue;
581 
582                     if (adminMinimumSecurityLevel <= securityLevel) {
583                         securityRestrictionPassed = true;
584                         break;
585                     }
586                 }
587                 if (!securityRestrictionPassed) {
588                     adminRestrictedSsid.append(scanId).append(" / ");
589                     continue;
590                 }
591             }
592 
593             // Skip network that has deprecated security type
594             if (mWifiGlobals.isWpaPersonalDeprecated() || mWifiGlobals.isWepDeprecated()) {
595                 boolean securityTypeDeprecated = false;
596                 @WifiAnnotations.SecurityType int[] securityTypes = scanResult.getSecurityTypes();
597                 for (int type : securityTypes) {
598                     if (mWifiGlobals.isWepDeprecated() && type == WifiInfo.SECURITY_TYPE_WEP) {
599                         securityTypeDeprecated = true;
600                         break;
601                     }
602                     if (mWifiGlobals.isWpaPersonalDeprecated() && type == WifiInfo.SECURITY_TYPE_PSK
603                             && ScanResultUtil.isScanResultForWpaPersonalOnlyNetwork(scanResult)) {
604                         securityTypeDeprecated = true;
605                         break;
606                     }
607                 }
608                 if (securityTypeDeprecated) {
609                     deprecatedSecurityTypeSsid.add(scanId);
610                     continue;
611                 }
612             }
613 
614             validScanDetails.add(scanDetail);
615         }
616         mWifiMetrics.incrementNetworkSelectionFilteredBssidCount(numBssidFiltered);
617 
618         // WNS listens to all single scan results. Some scan requests may not include
619         // the channel of the currently connected network, so the currently connected
620         // network won't show up in the scan results. We don't act on these scan results
621         // to avoid aggressive network switching which might trigger disconnection.
622         // TODO(b/147751334) this may no longer be needed
623         for (ClientModeManagerState cmmState : cmmStates) {
624             // TODO (b/169413079): Disable network selection on corresponding CMM instead.
625             if (cmmState.connected && cmmState.wifiInfo.getScore() >= WIFI_POOR_SCORE
626                     && !scanResultPresentForCurrentBssids.contains(cmmState.wifiInfo.getBSSID())) {
627                 if (isSufficiencyCheckEnabled()) {
628                     localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID()
629                             + " is not in the scan results. Skip network selection.");
630                     validScanDetails.clear();
631                     return validScanDetails;
632                 } else {
633                     localLog("Current connected BSSID " + cmmState.wifiInfo.getBSSID()
634                             + " is not in the scan results. But continue network selection because"
635                             + " sufficiency check is disabled.");
636                 }
637             }
638         }
639 
640         if (noValidSsid.length() != 0) {
641             localLog("Networks filtered out due to invalid SSID: " + noValidSsid);
642         }
643 
644         if (blockedBssid.length() != 0) {
645             localLog("Networks filtered out due to blocklist: " + blockedBssid);
646         }
647 
648         if (lowRssi.length() != 0) {
649             localLog("Networks filtered out due to low signal strength: " + lowRssi);
650         }
651 
652         if (mboAssociationDisallowedBssid.length() != 0) {
653             localLog("Networks filtered out due to mbo association disallowed indication: "
654                     + mboAssociationDisallowedBssid);
655         }
656 
657         if (adminRestrictedSsid.length() != 0) {
658             localLog("Networks filtered out due to admin restrictions: " + adminRestrictedSsid);
659         }
660 
661         if (deprecatedSecurityTypeSsid.length() != 0) {
662             localLog("Networks filtered out due to deprecated security type: "
663                     + deprecatedSecurityTypeSsid);
664         }
665 
666         return validScanDetails;
667     }
668 
findScanDetailForBssid(List<ScanDetail> scanDetails, String currentBssid)669     private ScanDetail findScanDetailForBssid(List<ScanDetail> scanDetails,
670             String currentBssid) {
671         for (ScanDetail scanDetail : scanDetails) {
672             ScanResult scanResult = scanDetail.getScanResult();
673             if (scanResult.BSSID.equals(currentBssid)) {
674                 return scanDetail;
675             }
676         }
677         return null;
678     }
679 
isEnhancedOpenSupported()680     private boolean isEnhancedOpenSupported() {
681         if (mIsEnhancedOpenSupportedInitialized) {
682             return mIsEnhancedOpenSupported;
683         }
684 
685         mIsEnhancedOpenSupportedInitialized = true;
686         ClientModeManager primaryManager =
687                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
688         mIsEnhancedOpenSupported = (primaryManager.getSupportedFeatures() & WIFI_FEATURE_OWE) != 0;
689         return mIsEnhancedOpenSupported;
690     }
691 
692     /**
693      * This returns a list of ScanDetails that were filtered in the process of network selection.
694      * The list is further filtered for only open unsaved networks.
695      *
696      * @return the list of ScanDetails for open unsaved networks that do not have invalid SSIDS,
697      * blocked BSSIDS, or low signal strength. This will return an empty list when there are
698      * no open unsaved networks, or when network selection has not been run.
699      */
getFilteredScanDetailsForOpenUnsavedNetworks()700     public List<ScanDetail> getFilteredScanDetailsForOpenUnsavedNetworks() {
701         List<ScanDetail> openUnsavedNetworks = new ArrayList<>();
702         boolean enhancedOpenSupported = isEnhancedOpenSupported();
703         for (ScanDetail scanDetail : mFilteredNetworks) {
704             ScanResult scanResult = scanDetail.getScanResult();
705 
706             if (!ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
707                 continue;
708             }
709 
710             // Filter out Enhanced Open networks on devices that do not support it
711             if (ScanResultUtil.isScanResultForOweNetwork(scanResult)
712                     && !enhancedOpenSupported) {
713                 continue;
714             }
715 
716             // Skip saved networks
717             if (mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail) != null) {
718                 continue;
719             }
720 
721             openUnsavedNetworks.add(scanDetail);
722         }
723         return openUnsavedNetworks;
724     }
725 
726     /**
727      * @return the list of ScanDetails scored as potential candidates by the last run of
728      * selectNetwork, this will be empty if Network selector determined no selection was
729      * needed on last run. This includes scan details of sufficient signal strength, and
730      * had an associated WifiConfiguration.
731      */
getConnectableScanDetails()732     public List<Pair<ScanDetail, WifiConfiguration>> getConnectableScanDetails() {
733         return mConnectableNetworks;
734     }
735 
736     /**
737      * Iterate thru the list of configured networks (includes all saved network configurations +
738      * any ephemeral network configurations created for passpoint networks, suggestions, carrier
739      * networks, etc) and do the following:
740      * a) Try to re-enable any temporarily enabled networks (if the blocklist duration has expired).
741      * b) Clear the {@link WifiConfiguration.NetworkSelectionStatus#getCandidate()} field for all
742      * of them to identify networks that are present in the current scan result.
743      * c) Log any disabled networks.
744      */
updateConfiguredNetworks()745     private void updateConfiguredNetworks() {
746         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
747         if (configuredNetworks.size() == 0) {
748             localLog("No configured networks.");
749             return;
750         }
751 
752         StringBuffer sbuf = new StringBuffer();
753         for (WifiConfiguration network : configuredNetworks) {
754             // If a configuration is temporarily disabled, re-enable it before trying
755             // to connect to it.
756             mWifiConfigManager.tryEnableNetwork(network.networkId);
757             // Clear the cached candidate, score and seen.
758             mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
759 
760             // Log disabled network.
761             WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
762             if (!status.isNetworkEnabled()) {
763                 sbuf.append("  ").append(toNetworkString(network)).append(" ");
764                 for (int index = WifiConfiguration.NetworkSelectionStatus
765                         .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
766                         index < WifiConfiguration.NetworkSelectionStatus
767                                 .NETWORK_SELECTION_DISABLED_MAX;
768                         index++) {
769                     int count = status.getDisableReasonCounter(index);
770                     // Here we log the reason as long as its count is greater than zero. The
771                     // network may not be disabled because of this particular reason. Logging
772                     // this information anyway to help understand what happened to the network.
773                     if (count > 0) {
774                         sbuf.append("reason=")
775                                 .append(WifiConfiguration.NetworkSelectionStatus
776                                         .getNetworkSelectionDisableReasonString(index))
777                                 .append(", count=").append(count).append("; ");
778                     }
779                 }
780                 sbuf.append("\n");
781             }
782         }
783 
784         if (sbuf.length() > 0) {
785             localLog("Disabled configured networks:");
786             localLog(sbuf.toString());
787         }
788     }
789 
790     /**
791      * Overrides the {@code candidate} chosen by the {@link #mNominators} with the user chosen
792      * {@link WifiConfiguration} if one exists.
793      *
794      * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise
795      */
overrideCandidateWithUserConnectChoice( @onNull WifiConfiguration candidate)796     private WifiConfiguration overrideCandidateWithUserConnectChoice(
797             @NonNull WifiConfiguration candidate) {
798         WifiConfiguration tempConfig = Preconditions.checkNotNull(candidate);
799         WifiConfiguration originalCandidate = candidate;
800         ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
801 
802         Set<String> seenNetworks = new HashSet<>();
803         seenNetworks.add(candidate.getProfileKey());
804 
805         while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
806             String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
807             int userSelectedRssi = tempConfig.getNetworkSelectionStatus().getConnectChoiceRssi();
808             tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
809 
810             if (tempConfig != null) {
811                 if (seenNetworks.contains(tempConfig.getProfileKey())) {
812                     Log.wtf(TAG, "user connected network is a loop, use candidate:"
813                             + candidate);
814                     if (candidate.getNetworkSelectionStatus().getCandidate() != null) {
815                         mWifiConfigManager.setLegacyUserConnectChoice(candidate,
816                                 candidate.getNetworkSelectionStatus().getCandidate().level);
817                     }
818                     break;
819                 }
820                 seenNetworks.add(tempConfig.getProfileKey());
821                 WifiConfiguration.NetworkSelectionStatus tempStatus =
822                         tempConfig.getNetworkSelectionStatus();
823                 boolean noInternetButInternetIsExpected = !tempConfig.isNoInternetAccessExpected()
824                         && tempConfig.hasNoInternetAccess();
825                 if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()
826                         && !noInternetButInternetIsExpected
827                         && isUserChoiceRssiCloseToOrGreaterThanExpectedValue(
828                                 tempStatus.getCandidate().level, userSelectedRssi)) {
829                     scanResultCandidate = tempStatus.getCandidate();
830                     candidate = tempConfig;
831                 }
832             } else {
833                 localLog("Connect choice: " + key + " has no corresponding saved config.");
834                 break;
835             }
836         }
837 
838         if (candidate != originalCandidate) {
839             localLog("After user selection adjustment, the final candidate is:"
840                     + WifiNetworkSelector.toNetworkString(candidate) + " : "
841                     + scanResultCandidate.BSSID);
842             mWifiMetrics.setNominatorForNetwork(candidate.networkId,
843                     WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE);
844         }
845         return candidate;
846     }
847 
isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi, int expectedRssi)848     private boolean isUserChoiceRssiCloseToOrGreaterThanExpectedValue(int observedRssi,
849             int expectedRssi) {
850         // The expectedRssi may be 0 for newly upgraded devices which do not have this information,
851         // pass the test for those devices to avoid regression.
852         if (expectedRssi == 0) {
853             return true;
854         }
855         return observedRssi >= expectedRssi - mScoringParams.getEstimateRssiErrorMargin();
856     }
857 
858 
859     /**
860      * Indicates whether we have ever seen the network to be metered since wifi was enabled.
861      *
862      * This is sticky to prevent continuous flip-flopping between networks, when the metered
863      * status is learned after association.
864      */
isEverMetered(@onNull WifiConfiguration config, @Nullable WifiInfo info, @NonNull ScanDetail scanDetail)865     private boolean isEverMetered(@NonNull WifiConfiguration config, @Nullable WifiInfo info,
866             @NonNull ScanDetail scanDetail) {
867         // If info does not match config, don't use it.
868         if (info != null && info.getNetworkId() != config.networkId) info = null;
869         boolean metered = WifiConfiguration.isMetered(config, info);
870         NetworkDetail networkDetail = scanDetail.getNetworkDetail();
871         if (networkDetail != null
872                 && networkDetail.getAnt()
873                 == NetworkDetail.Ant.ChargeablePublic) {
874             metered = true;
875         }
876         mWifiMetrics.addMeteredStat(config, metered);
877         if (config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE) {
878             // User override is in effect; we should trust it
879             if (mKnownMeteredNetworkIds.remove(config.networkId)) {
880                 localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
881             }
882             metered = config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
883         } else if (mKnownMeteredNetworkIds.contains(config.networkId)) {
884             // Use the saved information
885             metered = true;
886         } else if (metered) {
887             // Update the saved information
888             mKnownMeteredNetworkIds.add(config.networkId);
889             localLog("KnownMeteredNetworkIds = " + mKnownMeteredNetworkIds);
890         }
891         return metered;
892     }
893 
894     /**
895      * Returns the set of known metered network ids (for tests. dumpsys, and metrics).
896      */
getKnownMeteredNetworkIds()897     public Set<Integer> getKnownMeteredNetworkIds() {
898         return new ArraySet<>(mKnownMeteredNetworkIds);
899     }
900 
901     private final ArraySet<Integer> mKnownMeteredNetworkIds = new ArraySet<>();
902 
903 
904     /**
905      * Cleans up state that should go away when wifi is disabled.
906      */
resetOnDisable()907     public void resetOnDisable() {
908         mWifiConfigManager.clearLastSelectedNetwork();
909         mKnownMeteredNetworkIds.clear();
910     }
911 
912     /**
913      * Container class for passing the ClientModeManager state for each instance that is managed by
914      * WifiConnectivityManager, i.e all {@link ClientModeManager#getRole()} equals
915      * {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} or
916      * {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
917      */
918     public static class ClientModeManagerState {
919         /** Iface Name corresponding to iface (if known) */
920         public final String ifaceName;
921         /** True if the device is connected */
922         public final boolean connected;
923         /** True if the device is disconnected */
924         public final boolean disconnected;
925         /** True if the device is connected in local-only mode due to ip provisioning timeout**/
926         public final boolean ipProvisioningTimedOut;
927          /** Currently connected network */
928         public final WifiInfo wifiInfo;
929         public final ActiveModeManager.ClientRole role;
930 
ClientModeManagerState(@onNull ClientModeManager clientModeManager)931         ClientModeManagerState(@NonNull ClientModeManager clientModeManager) {
932             ifaceName = clientModeManager.getInterfaceName();
933             connected = clientModeManager.isConnected();
934             disconnected = clientModeManager.isDisconnected();
935             ipProvisioningTimedOut = clientModeManager.isIpProvisioningTimedOut();
936             wifiInfo = clientModeManager.getConnectionInfo();
937             role = clientModeManager.getRole();
938         }
939 
ClientModeManagerState()940         ClientModeManagerState() {
941             ifaceName = "unknown";
942             connected = false;
943             disconnected = true;
944             wifiInfo = new WifiInfo();
945             ipProvisioningTimedOut = false;
946             role = null;
947         }
948 
949         @VisibleForTesting
ClientModeManagerState(@onNull String ifaceName, boolean connected, boolean disconnected, @NonNull WifiInfo wifiInfo, boolean ipProvisioningTimedOut, ActiveModeManager.ClientRole role)950         ClientModeManagerState(@NonNull String ifaceName, boolean connected, boolean disconnected,
951                 @NonNull WifiInfo wifiInfo, boolean ipProvisioningTimedOut,
952                 ActiveModeManager.ClientRole role) {
953             this.ifaceName = ifaceName;
954             this.connected = connected;
955             this.disconnected = disconnected;
956             this.wifiInfo = wifiInfo;
957             this.ipProvisioningTimedOut = ipProvisioningTimedOut;
958             this.role = role;
959         }
960 
961         @Override
equals(Object that)962         public boolean equals(Object that) {
963             if (this == that) return true;
964             if (!(that instanceof ClientModeManagerState)) return false;
965             ClientModeManagerState thatCmmState = (ClientModeManagerState) that;
966             return Objects.equals(ifaceName, thatCmmState.ifaceName)
967                     && connected == thatCmmState.connected
968                     && disconnected == thatCmmState.disconnected
969                     && role == thatCmmState.role
970                     // Since wifiinfo does not have equals currently.
971                     && Objects.equals(wifiInfo.getSSID(), thatCmmState.wifiInfo.getSSID())
972                     && Objects.equals(wifiInfo.getBSSID(), thatCmmState.wifiInfo.getBSSID());
973         }
974 
975         @Override
hashCode()976         public int hashCode() {
977             return Objects.hash(ifaceName, connected, disconnected,
978                     wifiInfo.getSSID(), wifiInfo.getBSSID(), role);
979         }
980 
981         @Override
toString()982         public String toString() {
983             return "ClientModeManagerState: " + ifaceName
984                     + ", role:" + role
985                     + ", connection state: "
986                     + (connected ? " connected" : (disconnected ? " disconnected" : "unknown"))
987                     + ", WifiInfo: " + wifiInfo;
988         }
989     }
990 
991     /**
992      * Sets the screen state.
993      */
setScreenState(boolean screenOn)994     public void setScreenState(boolean screenOn) {
995         mScreenOn = screenOn;
996     }
997 
998     /**
999      * Sets the associated network selection override.
1000      */
setAssociatedNetworkSelectionOverride( @ssociatedNetworkSelectionOverride int value)1001     public boolean setAssociatedNetworkSelectionOverride(
1002             @AssociatedNetworkSelectionOverride int value) {
1003         if (value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_NONE
1004                 && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_ENABLED
1005                 && value != ASSOCIATED_NETWORK_SELECTION_OVERRIDE_DISABLED) {
1006             localLog("Error in setting associated network selection override. Invalid value="
1007                     + value);
1008             return false;
1009         }
1010         mAssociatedNetworkSelectionOverride = value;
1011         return true;
1012     }
1013 
1014     /**
1015      * Get whether sufficiency check is enabled based on the current screen state.
1016      */
isSufficiencyCheckEnabled()1017     public boolean isSufficiencyCheckEnabled() {
1018         return mScreenOn ? mSufficiencyCheckEnabledWhenScreenOn
1019                 : mSufficiencyCheckEnabledWhenScreenOff;
1020     }
1021 
1022     /**
1023      * Enable or disable sufficiency check.
1024      */
setSufficiencyCheckEnabled(boolean enabledWhileScreenOff, boolean enabledWhileScreenOn)1025     public void setSufficiencyCheckEnabled(boolean enabledWhileScreenOff,
1026             boolean enabledWhileScreenOn) {
1027         mSufficiencyCheckEnabledWhenScreenOff = enabledWhileScreenOff;
1028         mSufficiencyCheckEnabledWhenScreenOn = enabledWhileScreenOn;
1029     }
1030 
1031     /**
1032      * Enable or disable candidate override with user connect choice.
1033      */
setUserConnectChoiceOverrideEnabled(boolean enabled)1034     public void setUserConnectChoiceOverrideEnabled(boolean enabled) {
1035         mUserConnectChoiceOverrideEnabled = enabled;
1036     }
1037 
1038     /**
1039      * Enable or disable last selection weight.
1040      */
setLastSelectionWeightEnabled(boolean enabled)1041     public void setLastSelectionWeightEnabled(boolean enabled) {
1042         mLastSelectionWeightEnabled = enabled;
1043     }
1044 
getConnectChoiceKey(@onNull List<ClientModeManagerState> cmmStates)1045     private String getConnectChoiceKey(@NonNull List<ClientModeManagerState> cmmStates) {
1046         for (ClientModeManagerState cmmState : cmmStates) {
1047             if (cmmState.role != ROLE_CLIENT_PRIMARY) {
1048                 continue;
1049             }
1050             WifiConfiguration currentNetwork =
1051                     mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId());
1052             if (currentNetwork != null) {
1053                 return currentNetwork.getNetworkSelectionStatus().getConnectChoice();
1054             }
1055         }
1056         return null;
1057     }
1058 
1059     /**
1060      * Returns the list of Candidates from networks in range.
1061      *
1062      * @param scanDetails              List of ScanDetail for all the APs in range
1063      * @param bssidBlocklist           Blocked BSSIDs
1064      * @param cmmStates                State of all long lived client mode manager instances -
1065      *                                 {@link ActiveModeManager#ROLE_CLIENT_PRIMARY} &
1066      *                                 {@link ActiveModeManager#ROLE_CLIENT_SECONDARY_LONG_LIVED}.
1067      * @param untrustedNetworkAllowed  True if untrusted networks are allowed for connection
1068      * @param oemPaidNetworkAllowed    True if oem paid networks are allowed for connection
1069      * @param oemPrivateNetworkAllowed True if oem private networks are allowed for connection
1070      * @param restrictedNetworkAllowedUids a set of Uids are allowed for restricted network
1071      * @param skipSufficiencyCheck     True to skip network sufficiency check
1072      * @return list of valid Candidate(s)
1073      */
getCandidatesFromScan( @onNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist, @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed, boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed, Set<Integer> restrictedNetworkAllowedUids, boolean skipSufficiencyCheck)1074     public List<WifiCandidates.Candidate> getCandidatesFromScan(
1075             @NonNull List<ScanDetail> scanDetails, @NonNull Set<String> bssidBlocklist,
1076             @NonNull List<ClientModeManagerState> cmmStates, boolean untrustedNetworkAllowed,
1077             boolean oemPaidNetworkAllowed, boolean oemPrivateNetworkAllowed,
1078             Set<Integer> restrictedNetworkAllowedUids, boolean skipSufficiencyCheck) {
1079         mFilteredNetworks.clear();
1080         mConnectableNetworks.clear();
1081         if (scanDetails.size() == 0) {
1082             localLog("Empty connectivity scan result");
1083             return null;
1084         }
1085 
1086         // Update the scan detail cache at the start, even if we skip network selection
1087         updateScanDetailCache(scanDetails);
1088 
1089         // Update the registered network nominators.
1090         for (NetworkNominator registeredNominator : mNominators) {
1091             registeredNominator.update(scanDetails);
1092         }
1093 
1094         // Filter out unwanted networks.
1095         mFilteredNetworks = filterScanResults(scanDetails, bssidBlocklist, cmmStates);
1096         if (mFilteredNetworks.size() == 0) {
1097             return null;
1098         }
1099         // Update the matching profiles into WifiConfigManager, help displaying Passpoint networks
1100         // in Wifi Picker
1101         mWifiInjector.getPasspointNetworkNominateHelper().updatePasspointConfig(mFilteredNetworks);
1102 
1103         boolean networkSelectionNeeded = skipSufficiencyCheck
1104                 || isNetworkSelectionNeeded(cmmStates);
1105         final String userConnectChoiceKey;
1106         if (!networkSelectionNeeded) {
1107             if (!isAssociatedNetworkSelectionEnabled()) {
1108                 // Skip network selection based on connect choice because associated network
1109                 // selection is disabled.
1110                 return null;
1111             }
1112             userConnectChoiceKey = getConnectChoiceKey(cmmStates);
1113             if (userConnectChoiceKey == null) {
1114                 return null;
1115             }
1116             // Continue candidate selection but only allow the user connect choice as candidate
1117             localLog("Current network is sufficient. Continue network selection only "
1118                     + "considering user connect choice: " + userConnectChoiceKey);
1119         } else {
1120             userConnectChoiceKey = null;
1121         }
1122 
1123         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
1124         if (userConnectChoiceKey == null) {
1125             // Add connected network as candidates unless only considering connect choice.
1126             for (ClientModeManagerState cmmState : cmmStates) {
1127                 // Always get the current BSSID from WifiInfo in case that firmware initiated
1128                 // roaming happened.
1129                 String currentBssid = cmmState.wifiInfo.getBSSID();
1130                 WifiConfiguration currentNetwork =
1131                         mWifiConfigManager.getConfiguredNetwork(cmmState.wifiInfo.getNetworkId());
1132                 if (currentNetwork != null) {
1133                     wifiCandidates.setCurrent(currentNetwork.networkId, currentBssid);
1134                     // We always want the current network to be a candidate so that it can
1135                     // participate.
1136                     // It may also get re-added by a nominator, in which case this fallback
1137                     // will be replaced.
1138                     MacAddress bssid = MacAddress.fromString(currentBssid);
1139                     SecurityParams params = currentNetwork.getNetworkSelectionStatus()
1140                             .getLastUsedSecurityParams();
1141                     if (null == params) {
1142                         localLog("No known candidate security params for current network.");
1143                         continue;
1144                     }
1145                     WifiCandidates.Key key = new WifiCandidates.Key(
1146                             ScanResultMatchInfo.fromWifiConfiguration(currentNetwork),
1147                             bssid, currentNetwork.networkId,
1148                             params.getSecurityType());
1149                     ScanDetail scanDetail = findScanDetailForBssid(mFilteredNetworks, currentBssid);
1150                     int predictedTputMbps = (scanDetail == null) ? 0
1151                             : predictThroughput(scanDetail);
1152                     wifiCandidates.add(key, currentNetwork,
1153                             NetworkNominator.NOMINATOR_ID_CURRENT,
1154                             cmmState.wifiInfo.getRssi(),
1155                             cmmState.wifiInfo.getFrequency(),
1156                             ScanResult.CHANNEL_WIDTH_20MHZ, // channel width unavailable in WifiInfo
1157                             calculateLastSelectionWeight(currentNetwork.networkId,
1158                                     WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo)),
1159                             WifiConfiguration.isMetered(currentNetwork, cmmState.wifiInfo),
1160                             isFromCarrierOrPrivilegedApp(currentNetwork),
1161                             predictedTputMbps,
1162                             (scanDetail != null) ? scanDetail.getScanResult().getApMldMacAddress()
1163                                     : null);
1164                 }
1165             }
1166         }
1167 
1168         // Update all configured networks before initiating network selection.
1169         updateConfiguredNetworks();
1170 
1171         List<Pair<ScanDetail, WifiConfiguration>> passpointCandidates = mWifiInjector
1172                 .getPasspointNetworkNominateHelper()
1173                 .getPasspointNetworkCandidates(new ArrayList<>(mFilteredNetworks));
1174         for (NetworkNominator registeredNominator : mNominators) {
1175             localLog("About to run " + registeredNominator.getName() + " :");
1176             registeredNominator.nominateNetworks(
1177                     new ArrayList<>(mFilteredNetworks), passpointCandidates,
1178                     untrustedNetworkAllowed, oemPaidNetworkAllowed, oemPrivateNetworkAllowed,
1179                     restrictedNetworkAllowedUids, (scanDetail, config) -> {
1180                         WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
1181                                 scanDetail, config);
1182                         if (key != null) {
1183                             if (userConnectChoiceKey != null
1184                                     && !userConnectChoiceKey.equals(config.getProfileKey())) {
1185                                 return;
1186                             }
1187                             boolean metered = false;
1188                             for (ClientModeManagerState cmmState : cmmStates) {
1189                                 if (isEverMetered(config, cmmState.wifiInfo, scanDetail)) {
1190                                     metered = true;
1191                                     break;
1192                                 }
1193                             }
1194                             // TODO(b/151981920) Saved passpoint candidates are marked ephemeral
1195                             boolean added = wifiCandidates.add(key, config,
1196                                     registeredNominator.getId(),
1197                                     scanDetail.getScanResult().level,
1198                                     scanDetail.getScanResult().frequency,
1199                                     scanDetail.getScanResult().channelWidth,
1200                                     calculateLastSelectionWeight(config.networkId, metered),
1201                                     metered,
1202                                     isFromCarrierOrPrivilegedApp(config),
1203                                     predictThroughput(scanDetail),
1204                                     scanDetail.getScanResult().getApMldMacAddress());
1205                             if (added) {
1206                                 mConnectableNetworks.add(Pair.create(scanDetail, config));
1207                                 mWifiConfigManager.updateScanDetailForNetwork(
1208                                         config.networkId, scanDetail);
1209                                 mWifiMetrics.setNominatorForNetwork(config.networkId,
1210                                         toProtoNominatorId(registeredNominator.getId()));
1211                             }
1212                         }
1213                     });
1214         }
1215         if (mConnectableNetworks.size() != wifiCandidates.size()) {
1216             localLog("Connectable: " + mConnectableNetworks.size()
1217                     + " Candidates: " + wifiCandidates.size());
1218         }
1219 
1220         // Update multi link candidate throughput before network selection.
1221         updateMultiLinkCandidatesThroughput(wifiCandidates);
1222 
1223         return wifiCandidates.getCandidates();
1224     }
1225 
1226     /**
1227      * Check Wi-Fi7 is enabled for all candidates.
1228      */
isWifi7Enabled(List<WifiCandidates.Candidate> candidates)1229     private boolean isWifi7Enabled(List<WifiCandidates.Candidate> candidates) {
1230         for (WifiCandidates.Candidate candidate : candidates) {
1231             if (!mWifiConfigManager.isWifi7Enabled(candidate.getNetworkConfigId())) return false;
1232         }
1233         return true;
1234     }
1235 
1236     /**
1237      * Update multi link candidate's throughput which is used in network selection by
1238      * {@link ThroughputScorer}
1239      *
1240      * Algorithm:
1241      * {@link WifiNative#getSupportedBandCombinations(String)} returns a list of band combinations
1242      * supported by the chip. e.g. { {2.4}, {5}, {6}, {2.4, 5}, {2.4, 6}, {5, 6} }.
1243      *
1244      * During the creation of candidate list, members which have same MLD AP MAC address are grouped
1245      * together. Let's say we have the following multi link candidates in one group {C_2.4, C_5,
1246      * C_6}. First intersect this list with allowed combination to get a collection like this,
1247      * { {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,
1248      * predicted single link throughputs are added and each candidate in the subgroup get an
1249      * updated multi link throughput if the saved value is less. This calculation takes care of
1250      * eMLSR and STR.
1251      *
1252      * If the chip can't support all the radios for multi-link operation at the same time for STR
1253      * operation, we can't use the higher-order radio combinations.
1254      *
1255      * Above algorithm is extendable to multiple links with any number of bands and link
1256      * restriction.
1257      *
1258      * @param wifiCandidates A list of WifiCandidates
1259      */
updateMultiLinkCandidatesThroughput(WifiCandidates wifiCandidates)1260     private void updateMultiLinkCandidatesThroughput(WifiCandidates wifiCandidates) {
1261         ClientModeManager primaryManager =
1262                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
1263         if (primaryManager == null) return;
1264         String interfaceName = primaryManager.getInterfaceName();
1265         if (interfaceName == null) return;
1266 
1267         // Check if the chip has more than one MLO STR link support.
1268         int maxMloStrLinkCount = mWifiNative.getMaxMloStrLinkCount(interfaceName);
1269         if (maxMloStrLinkCount <= 1) return;
1270 
1271         Set<List<Integer>> simultaneousBandCombinations = mWifiNative.getSupportedBandCombinations(
1272                 interfaceName);
1273         if (simultaneousBandCombinations == null) return;
1274 
1275         for (List<WifiCandidates.Candidate> mlCandidates :
1276                 wifiCandidates.getMultiLinkCandidates()) {
1277             if (!isWifi7Enabled(mlCandidates)) continue;
1278             for (List<Integer> bands : simultaneousBandCombinations) {
1279                 // Limit the radios/bands to maximum STR link supported in multi link operation.
1280                 if (bands.size() > maxMloStrLinkCount) break;
1281                 List<Integer> strBandsToIntersect = new ArrayList<>(bands);
1282                 List<WifiCandidates.Candidate> strMlCandidates = intersectMlCandidatesWithStrBands(
1283                         mlCandidates, strBandsToIntersect);
1284                 if (strMlCandidates != null) {
1285                     aggregateStrMultiLinkThroughput(strMlCandidates);
1286                 }
1287             }
1288         }
1289     }
1290 
1291     /**
1292      * Return the intersection of STR band combinations and best Multi-Link Wi-Fi candidates.
1293      */
intersectMlCandidatesWithStrBands( @onNull List<WifiCandidates.Candidate> candidates, @NonNull List<Integer> bands)1294     private List<WifiCandidates.Candidate> intersectMlCandidatesWithStrBands(
1295             @NonNull List<WifiCandidates.Candidate> candidates, @NonNull List<Integer> bands) {
1296         // Sorting is needed here to make the best candidates first in the list.
1297         List<WifiCandidates.Candidate> intersectedCandidates = candidates.stream()
1298                 .sorted(Comparator.comparingInt(
1299                         WifiCandidates.Candidate::getPredictedThroughputMbps).reversed())
1300                 .filter(k -> {
1301                     int band = Integer.valueOf(ScanResult.toBand(k.getFrequency()));
1302                     if (bands.contains(band)) {
1303                         // Remove first occurrence as it is counted already.
1304                         bands.remove(bands.indexOf(band));
1305                         return true;
1306                     }
1307                     return false;
1308                 })
1309                 .collect(Collectors.toList());
1310         // Make sure all bands are intersected.
1311         return (bands.isEmpty()) ? intersectedCandidates : null;
1312     }
1313 
1314     /**
1315      * Aggregate the throughput of STR multi-link candidates.
1316      */
aggregateStrMultiLinkThroughput( @onNull List<WifiCandidates.Candidate> candidates)1317     private void aggregateStrMultiLinkThroughput(
1318             @NonNull List<WifiCandidates.Candidate> candidates) {
1319         // Add all throughputs.
1320         int predictedMlThroughput = candidates.stream()
1321                 .mapToInt(c -> c.getPredictedThroughputMbps())
1322                 .sum();
1323         // Check if an update needed for multi link throughput.
1324         candidates.stream()
1325                 .filter(c -> c.getPredictedMultiLinkThroughputMbps() < predictedMlThroughput)
1326                 .forEach(c -> c.setPredictedMultiLinkThroughputMbps(predictedMlThroughput));
1327     }
1328 
1329     /**
1330      * Add all results as candidates for the user selected network and let network selection
1331      * chooses the proper one for the user selected network.
1332      * @param config                  The configuration for the user selected network.
1333      * @param scanDetails              List of ScanDetail for the user selected network.
1334      * @return list of valid Candidate(s)
1335      */
getCandidatesForUserSelection( WifiConfiguration config, @NonNull List<ScanDetail> scanDetails)1336     public List<WifiCandidates.Candidate> getCandidatesForUserSelection(
1337             WifiConfiguration config, @NonNull List<ScanDetail> scanDetails) {
1338         if (scanDetails.size() == 0) {
1339             if (mVerboseLoggingEnabled) {
1340                 Log.d(TAG, "No scan result for the user selected network.");
1341                 return null;
1342             }
1343         }
1344 
1345         mConnectableNetworks.clear();
1346         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext);
1347         for (ScanDetail scanDetail: scanDetails) {
1348             WifiCandidates.Key key = wifiCandidates.keyFromScanDetailAndConfig(
1349                     scanDetail, config);
1350             if (null == key) continue;
1351 
1352             boolean added = wifiCandidates.add(key, config,
1353                     WifiNetworkSelector.NetworkNominator.NOMINATOR_ID_CURRENT,
1354                     scanDetail.getScanResult().level,
1355                     scanDetail.getScanResult().frequency,
1356                     scanDetail.getScanResult().channelWidth,
1357                     0.0 /* lastSelectionWeightBetweenZeroAndOne */,
1358                     false /* isMetered */,
1359                     WifiNetworkSelector.isFromCarrierOrPrivilegedApp(config),
1360                     predictThroughput(scanDetail), scanDetail.getScanResult().getApMldMacAddress());
1361             if (!added) continue;
1362 
1363             mConnectableNetworks.add(Pair.create(scanDetail, config));
1364             mWifiConfigManager.updateScanDetailForNetwork(
1365                     config.networkId, scanDetail);
1366         }
1367         return wifiCandidates.getCandidates();
1368     }
1369 
1370     /**
1371      * For transition networks with only legacy networks,
1372      * remove auto-upgrade type to use the legacy type to
1373      * avoid roaming issues between two types.
1374      */
removeAutoUpgradeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList, @WifiConfiguration.SecurityType int baseSecurityType, @WifiConfiguration.SecurityType int upgradableSecurityType, boolean isLegacyNetworkInRange, boolean isUpgradableTypeOnlyInRange, boolean isAutoUpgradeEnabled)1375     private void removeAutoUpgradeSecurityParamsIfNecessary(
1376             WifiConfiguration config,
1377             List<SecurityParams> scanResultParamsList,
1378             @WifiConfiguration.SecurityType int baseSecurityType,
1379             @WifiConfiguration.SecurityType int upgradableSecurityType,
1380             boolean isLegacyNetworkInRange,
1381             boolean isUpgradableTypeOnlyInRange,
1382             boolean isAutoUpgradeEnabled) {
1383         localLog("removeAutoUpgradeSecurityParamsIfNecessary:"
1384                 + " SSID: " + config.SSID
1385                 + " baseSecurityType: " + baseSecurityType
1386                 + " upgradableSecurityType: " + upgradableSecurityType
1387                 + " isLegacyNetworkInRange: " + isLegacyNetworkInRange
1388                 + " isUpgradableTypeOnlyInRange: " + isUpgradableTypeOnlyInRange
1389                 + " isAutoUpgradeEnabled: " + isAutoUpgradeEnabled);
1390         if (isAutoUpgradeEnabled) {
1391             // Consider removing the auto-upgraded type if legacy networks are in range.
1392             if (!isLegacyNetworkInRange) return;
1393             // If base params is disabled or removed, keep the auto-upgrade params.
1394             SecurityParams baseParams = config.getSecurityParams(baseSecurityType);
1395             if (null == baseParams || !baseParams.isEnabled()) return;
1396             // If there are APs with standalone-upgradeable security type is in range,
1397             // do not consider removing the auto-upgraded type.
1398             if (isUpgradableTypeOnlyInRange) return;
1399         }
1400 
1401         SecurityParams upgradableParams = config.getSecurityParams(upgradableSecurityType);
1402         if (null == upgradableParams) return;
1403         if (!upgradableParams.isAddedByAutoUpgrade()) return;
1404         localLog("Remove upgradable security type " + upgradableSecurityType + " for the network.");
1405         scanResultParamsList.removeIf(p -> p.isSecurityType(upgradableSecurityType));
1406     }
1407 
1408     /** Helper function to place all conditions which need to remove auto-upgrade types. */
removeSecurityParamsIfNecessary( WifiConfiguration config, List<SecurityParams> scanResultParamsList)1409     private void removeSecurityParamsIfNecessary(
1410             WifiConfiguration config,
1411             List<SecurityParams> scanResultParamsList) {
1412         // When offload is supported, both types are passed down.
1413         if (!mWifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) {
1414             removeAutoUpgradeSecurityParamsIfNecessary(
1415                     config, scanResultParamsList,
1416                     WifiConfiguration.SECURITY_TYPE_PSK,
1417                     WifiConfiguration.SECURITY_TYPE_SAE,
1418                     mScanRequestProxy.isWpa2PersonalOnlyNetworkInRange(config.SSID),
1419                     mScanRequestProxy.isWpa3PersonalOnlyNetworkInRange(config.SSID),
1420                     mWifiGlobals.isWpa3SaeUpgradeEnabled());
1421         }
1422         removeAutoUpgradeSecurityParamsIfNecessary(
1423                 config, scanResultParamsList,
1424                 WifiConfiguration.SECURITY_TYPE_OPEN,
1425                 WifiConfiguration.SECURITY_TYPE_OWE,
1426                 mScanRequestProxy.isOpenOnlyNetworkInRange(config.SSID),
1427                 mScanRequestProxy.isOweOnlyNetworkInRange(config.SSID),
1428                 mWifiGlobals.isOweUpgradeEnabled());
1429         removeAutoUpgradeSecurityParamsIfNecessary(
1430                 config, scanResultParamsList,
1431                 WifiConfiguration.SECURITY_TYPE_EAP,
1432                 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
1433                 mScanRequestProxy.isWpa2EnterpriseOnlyNetworkInRange(config.SSID),
1434                 mScanRequestProxy.isWpa3EnterpriseOnlyNetworkInRange(config.SSID),
1435                 true);
1436         // When using WPA3 (SAE), all passwords in all lengths are strings, but when using WPA2,
1437         // there is a distinction between 8-63 octets that go through BDKDF2 function, and
1438         // 64-octets that are assumed to be the output of it. BDKDF2 is not applicable to SAE
1439         // and to prevent interop issues with APs when 64-octet Hex PSK is configured, update
1440         // the configuration to use WPA2 only.
1441         WifiConfiguration configWithPassword = mWifiConfigManager
1442                 .getConfiguredNetworkWithPassword(config.networkId);
1443         if (configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
1444                 && configWithPassword.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
1445                 && configWithPassword.preSharedKey != null
1446                 && !configWithPassword.preSharedKey.startsWith("\"")
1447                 && configWithPassword.preSharedKey.length() == 64
1448                 && configWithPassword.preSharedKey.matches("[0-9A-Fa-f]{64}")) {
1449             localLog("Remove SAE type for " + configWithPassword.SSID + " with 64-octet Hex PSK.");
1450             scanResultParamsList
1451                     .removeIf(p -> p.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE));
1452         }
1453     }
1454 
1455     /**
1456      * For the transition mode, MFPC should be true, and MFPR should be false,
1457      * see WPA3 SAE specification section 2.3 and 3.3.
1458      */
updateSecurityParamsForTransitionModeIfNecessary( ScanResult scanResult, SecurityParams params)1459     private void updateSecurityParamsForTransitionModeIfNecessary(
1460             ScanResult scanResult, SecurityParams params) {
1461         if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
1462                 && params.isAddedByAutoUpgrade()
1463                 && ScanResultUtil.isScanResultForPskSaeTransitionNetwork(scanResult)) {
1464             params.setRequirePmf(false);
1465         } else if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)
1466                 && params.isAddedByAutoUpgrade()
1467                 && ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
1468             params.setRequirePmf(false);
1469         }
1470     }
1471 
1472     /**
1473      * Update the candidate security params against a scan detail.
1474      *
1475      * @param network the target network.
1476      * @param scanDetail the target scan detail.
1477      */
updateNetworkCandidateSecurityParams( WifiConfiguration network, ScanDetail scanDetail)1478     private void updateNetworkCandidateSecurityParams(
1479             WifiConfiguration network, ScanDetail scanDetail) {
1480         if (network == null) return;
1481         if (scanDetail == null) return;
1482 
1483         ScanResult scanResult = scanDetail.getScanResult();
1484         List<SecurityParams> scanResultParamsList = ScanResultUtil
1485                 .generateSecurityParamsListFromScanResult(scanResult);
1486         if (scanResultParamsList == null) return;
1487         // Under some conditions, the legacy type is preferred to have better
1488         // connectivity behaviors, and the auto-upgrade type should be removed.
1489         removeSecurityParamsIfNecessary(network, scanResultParamsList);
1490         SecurityParams params = ScanResultMatchInfo.getBestMatchingSecurityParams(
1491                 network,
1492                 scanResultParamsList);
1493         if (params == null) return;
1494         updateSecurityParamsForTransitionModeIfNecessary(scanResult, params);
1495         mWifiConfigManager.setNetworkCandidateScanResult(
1496                 network.networkId, scanResult, 0, params);
1497     }
1498 
1499     /**
1500      * Using the registered Scorers, choose the best network from the list of Candidate(s).
1501      * The ScanDetailCache is also updated here.
1502      * @param candidates - Candidates to perferm network selection on.
1503      * @return WifiConfiguration - the selected network, or null.
1504      */
1505     @Nullable
selectNetwork(@onNull List<WifiCandidates.Candidate> candidates)1506     public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates) {
1507         return selectNetwork(candidates, true);
1508     }
1509 
1510     /**
1511      * Using the registered Scorers, choose the best network from the list of Candidate(s).
1512      * The ScanDetailCache is also updated here.
1513      * @param candidates - Candidates to perform network selection on.
1514      * @param overrideEnabled If it is allowed to override candidate with User Connect Choice.
1515      * @return WifiConfiguration - the selected network, or null.
1516      */
1517     @Nullable
selectNetwork(@onNull List<WifiCandidates.Candidate> candidates, boolean overrideEnabled)1518     public WifiConfiguration selectNetwork(@NonNull List<WifiCandidates.Candidate> candidates,
1519             boolean overrideEnabled) {
1520         if (candidates == null || candidates.size() == 0) {
1521             return null;
1522         }
1523         WifiCandidates wifiCandidates = new WifiCandidates(mWifiScoreCard, mContext, candidates);
1524         final WifiCandidates.CandidateScorer activeScorer = getActiveCandidateScorer();
1525         // Update the NetworkSelectionStatus in the configs for the current candidates
1526         // This is needed for the legacy user connect choice, at least
1527         Collection<Collection<WifiCandidates.Candidate>> groupedCandidates =
1528                 wifiCandidates.getGroupedCandidates();
1529         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
1530             WifiCandidates.ScoredCandidate choice = activeScorer.scoreCandidates(group);
1531             if (choice == null) continue;
1532             ScanDetail scanDetail = getScanDetailForCandidateKey(choice.candidateKey);
1533             if (scanDetail == null) continue;
1534             WifiConfiguration config = mWifiConfigManager
1535                     .getConfiguredNetwork(choice.candidateKey.networkId);
1536             if (config == null) continue;
1537             updateNetworkCandidateSecurityParams(config, scanDetail);
1538         }
1539 
1540         for (Collection<WifiCandidates.Candidate> group : groupedCandidates) {
1541             for (WifiCandidates.Candidate candidate : group.stream()
1542                     .sorted((a, b) -> (b.getScanRssi() - a.getScanRssi())) // decreasing rssi
1543                     .collect(Collectors.toList())) {
1544                 localLog(candidate.toString());
1545             }
1546         }
1547 
1548         ArrayMap<Integer, Integer> experimentNetworkSelections = new ArrayMap<>(); // for metrics
1549 
1550         int selectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
1551 
1552         // Run all the CandidateScorers
1553         boolean legacyOverrideWanted = true;
1554         for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
1555             WifiCandidates.ScoredCandidate choice;
1556             try {
1557                 choice = wifiCandidates.choose(candidateScorer);
1558             } catch (RuntimeException e) {
1559                 Log.wtf(TAG, "Exception running a CandidateScorer", e);
1560                 continue;
1561             }
1562             int networkId = choice.candidateKey == null
1563                     ? WifiConfiguration.INVALID_NETWORK_ID
1564                     : choice.candidateKey.networkId;
1565             String chooses = " would choose ";
1566             if (candidateScorer == activeScorer) {
1567                 chooses = " chooses ";
1568                 legacyOverrideWanted = choice.userConnectChoiceOverride;
1569                 selectedNetworkId = networkId;
1570                 updateChosenPasspointNetwork(choice);
1571             }
1572             String id = candidateScorer.getIdentifier();
1573             int expid = experimentIdFromIdentifier(id);
1574             localLog(id + chooses + networkId
1575                     + " score " + choice.value + "+/-" + choice.err
1576                     + " expid " + expid);
1577             experimentNetworkSelections.put(expid, networkId);
1578         }
1579 
1580         // Update metrics about differences in the selections made by various methods
1581         final int activeExperimentId = experimentIdFromIdentifier(activeScorer.getIdentifier());
1582         for (Map.Entry<Integer, Integer> entry :
1583                 experimentNetworkSelections.entrySet()) {
1584             int experimentId = entry.getKey();
1585             if (experimentId == activeExperimentId) continue;
1586             int thisSelectedNetworkId = entry.getValue();
1587             mWifiMetrics.logNetworkSelectionDecision(experimentId, activeExperimentId,
1588                     selectedNetworkId == thisSelectedNetworkId,
1589                     groupedCandidates.size());
1590         }
1591 
1592         // Get a fresh copy of WifiConfiguration reflecting any scan result updates
1593         WifiConfiguration selectedNetwork =
1594                 mWifiConfigManager.getConfiguredNetwork(selectedNetworkId);
1595         if (selectedNetwork != null && legacyOverrideWanted && overrideEnabled
1596                 && mUserConnectChoiceOverrideEnabled) {
1597             selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork);
1598         }
1599         if (selectedNetwork != null) {
1600             mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
1601         }
1602         return selectedNetwork;
1603     }
1604 
1605     /**
1606      * Returns the ScanDetail given the candidate key, using the saved list of connectible networks.
1607      */
getScanDetailForCandidateKey(WifiCandidates.Key candidateKey)1608     private ScanDetail getScanDetailForCandidateKey(WifiCandidates.Key candidateKey) {
1609         if (candidateKey == null) return null;
1610         String bssid = candidateKey.bssid.toString();
1611         for (Pair<ScanDetail, WifiConfiguration> pair : mConnectableNetworks) {
1612             if (candidateKey.networkId == pair.second.networkId
1613                     && bssid.equals(pair.first.getBSSIDString())) {
1614                 return pair.first;
1615             }
1616         }
1617         return null;
1618     }
1619 
updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice)1620     private void updateChosenPasspointNetwork(WifiCandidates.ScoredCandidate choice) {
1621         if (choice.candidateKey == null) {
1622             return;
1623         }
1624         WifiConfiguration config =
1625                 mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId);
1626         if (config == null) {
1627             return;
1628         }
1629         if (config.isPasspoint()) {
1630             config.SSID = choice.candidateKey.matchInfo.networkSsid;
1631             mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName,
1632                     false);
1633         }
1634     }
1635 
updateScanDetailCache(List<ScanDetail> scanDetails)1636     private void updateScanDetailCache(List<ScanDetail> scanDetails) {
1637         for (ScanDetail scanDetail : scanDetails) {
1638             mWifiConfigManager.updateScanDetailCacheFromScanDetailForSavedNetwork(scanDetail);
1639         }
1640     }
1641 
toProtoNominatorId(@etworkNominator.NominatorId int nominatorId)1642     private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) {
1643         switch (nominatorId) {
1644             case NetworkNominator.NOMINATOR_ID_SAVED:
1645                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED;
1646             case NetworkNominator.NOMINATOR_ID_SUGGESTION:
1647                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_SUGGESTION;
1648             case NetworkNominator.NOMINATOR_ID_SCORED:
1649                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_EXTERNAL_SCORED;
1650             case NetworkNominator.NOMINATOR_ID_CURRENT:
1651                 Log.e(TAG, "Unexpected NOMINATOR_ID_CURRENT", new RuntimeException());
1652                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
1653             default:
1654                 Log.e(TAG, "UnrecognizedNominatorId" + nominatorId);
1655                 return WifiMetricsProto.ConnectionEvent.NOMINATOR_UNKNOWN;
1656         }
1657     }
1658 
calculateLastSelectionWeight(int networkId, boolean isMetered)1659     private double calculateLastSelectionWeight(int networkId, boolean isMetered) {
1660         if (!mLastSelectionWeightEnabled
1661                 || networkId != mWifiConfigManager.getLastSelectedNetwork()) {
1662             return 0.0;
1663         }
1664         double timeDifference = mClock.getElapsedSinceBootMillis()
1665                 - mWifiConfigManager.getLastSelectedTimeStamp();
1666         long millis = TimeUnit.MINUTES.toMillis(isMetered
1667                 ? mScoringParams.getLastMeteredSelectionMinutes()
1668                 : mScoringParams.getLastUnmeteredSelectionMinutes());
1669         if (timeDifference >= millis) return 0.0;
1670         double unclipped = 1.0 - (timeDifference / millis);
1671         return Math.min(Math.max(unclipped, 0.0), 1.0);
1672     }
1673 
getActiveCandidateScorer()1674     private WifiCandidates.CandidateScorer getActiveCandidateScorer() {
1675         WifiCandidates.CandidateScorer ans = mCandidateScorers.get(PRESET_CANDIDATE_SCORER_NAME);
1676         int overrideExperimentId = mScoringParams.getExperimentIdentifier();
1677         if (overrideExperimentId >= MIN_SCORER_EXP_ID) {
1678             for (WifiCandidates.CandidateScorer candidateScorer : mCandidateScorers.values()) {
1679                 int expId = experimentIdFromIdentifier(candidateScorer.getIdentifier());
1680                 if (expId == overrideExperimentId) {
1681                     ans = candidateScorer;
1682                     break;
1683                 }
1684             }
1685         }
1686         if (ans == null && PRESET_CANDIDATE_SCORER_NAME != null) {
1687             Log.wtf(TAG, PRESET_CANDIDATE_SCORER_NAME + " is not registered!");
1688         }
1689         mWifiMetrics.setNetworkSelectorExperimentId(ans == null
1690                 ? LEGACY_CANDIDATE_SCORER_EXP_ID
1691                 : experimentIdFromIdentifier(ans.getIdentifier()));
1692         return ans;
1693     }
1694 
predictThroughput(@onNull ScanDetail scanDetail)1695     private int predictThroughput(@NonNull ScanDetail scanDetail) {
1696         if (scanDetail.getScanResult() == null || scanDetail.getNetworkDetail() == null) {
1697             return 0;
1698         }
1699         int channelUtilizationLinkLayerStats = BssLoad.INVALID;
1700         if (mWifiChannelUtilization != null) {
1701             channelUtilizationLinkLayerStats =
1702                     mWifiChannelUtilization.getUtilizationRatio(
1703                             scanDetail.getScanResult().frequency);
1704         }
1705         ClientModeManager primaryManager =
1706                 mWifiInjector.getActiveModeWarden().getPrimaryClientModeManager();
1707         return mThroughputPredictor.predictThroughput(
1708                 primaryManager.getDeviceWiphyCapabilities(),
1709                 scanDetail.getScanResult().getWifiStandard(),
1710                 scanDetail.getScanResult().channelWidth,
1711                 scanDetail.getScanResult().level,
1712                 scanDetail.getScanResult().frequency,
1713                 scanDetail.getNetworkDetail().getMaxNumberSpatialStreams(),
1714                 scanDetail.getNetworkDetail().getChannelUtilization(),
1715                 channelUtilizationLinkLayerStats,
1716                 mWifiGlobals.isBluetoothConnected(),
1717                 scanDetail.getNetworkDetail().getDisabledSubchannelBitmap());
1718     }
1719 
1720     /**
1721      * Register a network nominator
1722      *
1723      * @param nominator the network nominator to be registered
1724      */
registerNetworkNominator(@onNull NetworkNominator nominator)1725     public void registerNetworkNominator(@NonNull NetworkNominator nominator) {
1726         mNominators.add(Preconditions.checkNotNull(nominator));
1727     }
1728 
1729     /**
1730      * Register a candidate scorer.
1731      *
1732      * Replaces any existing scorer having the same identifier.
1733      */
registerCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1734     public void registerCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1735         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1736         if (name != null) {
1737             mCandidateScorers.put(name, candidateScorer);
1738         }
1739     }
1740 
1741     /**
1742      * Unregister a candidate scorer.
1743      */
unregisterCandidateScorer(@onNull WifiCandidates.CandidateScorer candidateScorer)1744     public void unregisterCandidateScorer(@NonNull WifiCandidates.CandidateScorer candidateScorer) {
1745         String name = Preconditions.checkNotNull(candidateScorer).getIdentifier();
1746         if (name != null) {
1747             mCandidateScorers.remove(name);
1748         }
1749     }
1750 
1751     /**
1752      * Indicate whether or not a configuration is from carrier or privileged app.
1753      *
1754      * @param config The network configuration
1755      * @return true if this configuration is from carrier or privileged app; false otherwise.
1756      */
isFromCarrierOrPrivilegedApp(WifiConfiguration config)1757     public static boolean isFromCarrierOrPrivilegedApp(WifiConfiguration config) {
1758         if (config.fromWifiNetworkSuggestion
1759                 && config.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
1760             // Privileged carrier suggestion
1761             return true;
1762         }
1763         if (config.isEphemeral()
1764                 && !config.fromWifiNetworkSpecifier
1765                 && !config.fromWifiNetworkSuggestion) {
1766             // From ScoredNetworkNominator
1767             return true;
1768         }
1769         return false;
1770     }
1771 
1772     /**
1773      * Derives a numeric experiment identifier from a CandidateScorer's identifier.
1774      *
1775      * @returns a positive number that starts with the decimal digits ID_PREFIX
1776      */
experimentIdFromIdentifier(String id)1777     public static int experimentIdFromIdentifier(String id) {
1778         final int digits = (int) (((long) id.hashCode()) & Integer.MAX_VALUE) % ID_SUFFIX_MOD;
1779         return ID_PREFIX * ID_SUFFIX_MOD + digits;
1780     }
1781 
1782     private static final int ID_SUFFIX_MOD = 1_000_000;
1783     private static final int ID_PREFIX = 42;
1784     private static final int MIN_SCORER_EXP_ID = ID_PREFIX * ID_SUFFIX_MOD;
1785 
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)1786     WifiNetworkSelector(
1787             Context context,
1788             WifiScoreCard wifiScoreCard,
1789             ScoringParams scoringParams,
1790             WifiConfigManager configManager,
1791             Clock clock,
1792             LocalLog localLog,
1793             WifiMetrics wifiMetrics,
1794             WifiInjector wifiInjector,
1795             ThroughputPredictor throughputPredictor,
1796             WifiChannelUtilization wifiChannelUtilization,
1797             WifiGlobals wifiGlobals,
1798             ScanRequestProxy scanRequestProxy,
1799             WifiNative wifiNative) {
1800         mContext = context;
1801         mWifiScoreCard = wifiScoreCard;
1802         mScoringParams = scoringParams;
1803         mWifiConfigManager = configManager;
1804         mClock = clock;
1805         mLocalLog = localLog;
1806         mWifiMetrics = wifiMetrics;
1807         mWifiInjector = wifiInjector;
1808         mThroughputPredictor = throughputPredictor;
1809         mWifiChannelUtilization = wifiChannelUtilization;
1810         mWifiGlobals = wifiGlobals;
1811         mScanRequestProxy = scanRequestProxy;
1812         mWifiNative = wifiNative;
1813         mDevicePolicyManager = WifiPermissionsUtil.retrieveDevicePolicyManagerFromContext(mContext);
1814     }
1815 }
1816