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