• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.WifiConfiguration.NetworkSelectionStatus.DISABLE_REASON_INFOS;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.net.wifi.ScanResult;
26 import android.net.wifi.WifiConfiguration;
27 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
28 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo;
29 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason;
30 import android.net.wifi.WifiSsid;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.LocalLog;
34 import android.util.Log;
35 import android.util.SparseArray;
36 
37 import androidx.annotation.Keep;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.server.wifi.util.StringUtil;
41 import com.android.server.wifi.util.WifiPermissionsUtil;
42 import com.android.wifi.resources.R;
43 
44 import java.io.FileDescriptor;
45 import java.io.PrintWriter;
46 import java.lang.annotation.Retention;
47 import java.lang.annotation.RetentionPolicy;
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.Collections;
51 import java.util.HashSet;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.concurrent.TimeUnit;
58 import java.util.stream.Collectors;
59 import java.util.stream.Stream;
60 
61 /**
62  * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used
63  * for firmware roaming and network selection.
64  */
65 public class WifiBlocklistMonitor {
66     // A special type association rejection
67     public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0;
68     // No internet
69     public static final int REASON_NETWORK_VALIDATION_FAILURE = 1;
70     // Wrong password error
71     public static final int REASON_WRONG_PASSWORD = 2;
72     // Incorrect EAP credentials
73     public static final int REASON_EAP_FAILURE = 3;
74     // Other association rejection failures
75     public static final int REASON_ASSOCIATION_REJECTION = 4;
76     // Association timeout failures.
77     public static final int REASON_ASSOCIATION_TIMEOUT = 5;
78     // Other authentication failures
79     public static final int REASON_AUTHENTICATION_FAILURE = 6;
80     // DHCP failures
81     public static final int REASON_DHCP_FAILURE = 7;
82     // Abnormal disconnect error
83     public static final int REASON_ABNORMAL_DISCONNECT = 8;
84     // AP initiated disconnect for a given duration.
85     public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9;
86     // Avoid connecting to the failed AP when trying to reconnect on other available candidates.
87     public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10;
88     // The connected scorer has disconnected this network.
89     public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11;
90     // Non-local disconnection in the middle of connecting state
91     public static final int REASON_NONLOCAL_DISCONNECT_CONNECTING = 12;
92     // Connection attempt aborted by the watchdog because the AP didn't respond.
93     public static final int REASON_FAILURE_NO_RESPONSE = 13;
94     public static final int REASON_APP_DISALLOW = 14;
95     // Constant being used to keep track of how many failure reasons there are.
96     public static final int NUMBER_REASON_CODES = 15;
97     public static final int INVALID_REASON = -1;
98 
99     @IntDef(prefix = { "REASON_" }, value = {
100             REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
101             REASON_NETWORK_VALIDATION_FAILURE,
102             REASON_WRONG_PASSWORD,
103             REASON_EAP_FAILURE,
104             REASON_ASSOCIATION_REJECTION,
105             REASON_ASSOCIATION_TIMEOUT,
106             REASON_AUTHENTICATION_FAILURE,
107             REASON_DHCP_FAILURE,
108             REASON_ABNORMAL_DISCONNECT,
109             REASON_FRAMEWORK_DISCONNECT_MBO_OCE,
110             REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT,
111             REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
112             REASON_NONLOCAL_DISCONNECT_CONNECTING,
113             REASON_FAILURE_NO_RESPONSE,
114             REASON_APP_DISALLOW
115     })
116     @Retention(RetentionPolicy.SOURCE)
117     public @interface FailureReason {}
118 
119     // To be filled with values from the overlay.
120     private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES];
121     private boolean mFailureCountDisableThresholdArrayInitialized = false;
122     private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
123     private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
124     @VisibleForTesting
125     public static final int NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF = 5;
126     private static final String TAG = "WifiBlocklistMonitor";
127 
128     private final Context mContext;
129     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
130     private final WifiConnectivityHelper mConnectivityHelper;
131     private final Clock mClock;
132     private final LocalLog mLocalLog;
133     private final WifiScoreCard mWifiScoreCard;
134     private final ScoringParams mScoringParams;
135     private final WifiMetrics mWifiMetrics;
136     private final WifiPermissionsUtil mWifiPermissionsUtil;
137     private ScanRequestProxy mScanRequestProxy;
138     private final Map<Integer, BssidDisableReason> mBssidDisableReasons =
139             buildBssidDisableReasons();
140     private final SparseArray<DisableReasonInfo> mDisableReasonInfo;
141     private final WifiGlobals mWifiGlobals;
142 
143     // Map of bssid to BssidStatus
144     private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>();
145     private Set<String> mDisabledSsids = new ArraySet<>();
146 
147     // Internal logger to make sure imporatant logs do not get lost.
148     private BssidBlocklistMonitorLogger mBssidBlocklistMonitorLogger =
149             new BssidBlocklistMonitorLogger(60);
150 
151     // Map of ssid to Allowlist SSIDs
152     private Map<String, List<String>> mSsidAllowlistMap = new ArrayMap<>();
153     private Set<WifiSsid> mSsidsAllowlistForNetworkSelection = new ArraySet<>();
154 
155     /**
156      * Verbose logging flag. Toggled by developer options.
157      */
158     private boolean mVerboseLoggingEnabled = false;
159 
160 
buildBssidDisableReasons()161     private Map<Integer, BssidDisableReason> buildBssidDisableReasons() {
162         Map<Integer, BssidDisableReason> result = new ArrayMap<>();
163         result.put(REASON_AP_UNABLE_TO_HANDLE_NEW_STA, new BssidDisableReason(
164                 "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", false, false));
165         result.put(REASON_NETWORK_VALIDATION_FAILURE, new BssidDisableReason(
166                 "REASON_NETWORK_VALIDATION_FAILURE", true, false));
167         result.put(REASON_WRONG_PASSWORD, new BssidDisableReason(
168                 "REASON_WRONG_PASSWORD", false, true));
169         result.put(REASON_EAP_FAILURE, new BssidDisableReason(
170                 "REASON_EAP_FAILURE", true, true));
171         result.put(REASON_ASSOCIATION_REJECTION, new BssidDisableReason(
172                 "REASON_ASSOCIATION_REJECTION", true, true));
173         result.put(REASON_ASSOCIATION_TIMEOUT, new BssidDisableReason(
174                 "REASON_ASSOCIATION_TIMEOUT", true, true));
175         result.put(REASON_AUTHENTICATION_FAILURE, new BssidDisableReason(
176                 "REASON_AUTHENTICATION_FAILURE", true, true));
177         result.put(REASON_DHCP_FAILURE, new BssidDisableReason(
178                 "REASON_DHCP_FAILURE", true, false));
179         result.put(REASON_ABNORMAL_DISCONNECT, new BssidDisableReason(
180                 "REASON_ABNORMAL_DISCONNECT", true, false));
181         result.put(REASON_FRAMEWORK_DISCONNECT_MBO_OCE, new BssidDisableReason(
182                 "REASON_FRAMEWORK_DISCONNECT_MBO_OCE", false, false));
183         result.put(REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, new BssidDisableReason(
184                 "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT", false, false));
185         result.put(REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, new BssidDisableReason(
186                 "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE", true, false));
187         // TODO: b/174166637, add the same reason code in SSID blocklist and mark ignoreIfOnlyBssid
188         // to true once it is covered in SSID blocklist.
189         result.put(REASON_NONLOCAL_DISCONNECT_CONNECTING, new BssidDisableReason(
190                 "REASON_NONLOCAL_DISCONNECT_CONNECTING", true, false));
191         result.put(REASON_FAILURE_NO_RESPONSE, new BssidDisableReason(
192                 "REASON_FAILURE_NO_RESPONSE", true, true));
193         result.put(REASON_APP_DISALLOW, new BssidDisableReason(
194                 "REASON_APP_DISALLOW", false, false));
195         return result;
196     }
197 
198     class BssidDisableReason {
199         public final String reasonString;
200         public final boolean isLowRssiSensitive;
201         public final boolean ignoreIfOnlyBssid;
202 
BssidDisableReason(String reasonString, boolean isLowRssiSensitive, boolean ignoreIfOnlyBssid)203         BssidDisableReason(String reasonString, boolean isLowRssiSensitive,
204                 boolean ignoreIfOnlyBssid) {
205             this.reasonString = reasonString;
206             this.isLowRssiSensitive = isLowRssiSensitive;
207             this.ignoreIfOnlyBssid = ignoreIfOnlyBssid;
208         }
209     }
210 
211     /** Map of BSSID to affiliated BSSIDs. */
212     private Map<String, List<String>> mAffiliatedBssidMap = new ArrayMap<>();
213 
214     /**
215      * Set the mapping of BSSID to affiliated BSSIDs.
216      *
217      * @param bssid A unique identifier of the AP.
218      * @param bssids List of affiliated BSSIDs.
219      */
setAffiliatedBssids(@onNull String bssid, @NonNull List<String> bssids)220     public void setAffiliatedBssids(@NonNull String bssid, @NonNull List<String> bssids) {
221         mAffiliatedBssidMap.put(bssid, bssids);
222     }
223 
224     /**
225      *  Get affiliated BSSIDs mapped to a BSSID.
226      *
227      * @param bssid A unique identifier of the AP.
228      * @return List of affiliated BSSIDs or an empty list.
229      */
getAffiliatedBssids(@onNull String bssid)230     public List<String> getAffiliatedBssids(@NonNull String bssid) {
231         List<String> affiliatedBssids = mAffiliatedBssidMap.get(bssid);
232         return affiliatedBssids == null ? Collections.EMPTY_LIST : affiliatedBssids;
233     }
234 
235     /**
236      * Remove affiliated BSSIDs mapped to a BSSID.
237      *
238      * @param bssid A unique identifier of the AP.
239      */
removeAffiliatedBssids(@onNull String bssid)240     public void removeAffiliatedBssids(@NonNull String bssid) {
241         mAffiliatedBssidMap.remove(bssid);
242     }
243 
244     /** Clear affiliated BSSID mapping table. */
clearAffiliatedBssids()245     public void clearAffiliatedBssids() {
246         mAffiliatedBssidMap.clear();
247     }
248 
249     /** Sets the ScanRequestProxy **/
setScanRequestProxy(ScanRequestProxy scanRequestProxy)250     public void setScanRequestProxy(ScanRequestProxy scanRequestProxy) {
251         mScanRequestProxy = scanRequestProxy;
252     }
253 
254     /**
255      * Create a new instance of WifiBlocklistMonitor
256      */
WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics, WifiPermissionsUtil wifiPermissionsUtil, WifiGlobals wifiGlobals)257     WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper,
258             WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog,
259             WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics,
260             WifiPermissionsUtil wifiPermissionsUtil, WifiGlobals wifiGlobals) {
261         mContext = context;
262         mConnectivityHelper = connectivityHelper;
263         mWifiLastResortWatchdog = wifiLastResortWatchdog;
264         mClock = clock;
265         mLocalLog = localLog;
266         mWifiScoreCard = wifiScoreCard;
267         mScoringParams = scoringParams;
268         mDisableReasonInfo = DISABLE_REASON_INFOS.clone();
269         mWifiMetrics = wifiMetrics;
270         mWifiPermissionsUtil = wifiPermissionsUtil;
271         mWifiGlobals = wifiGlobals;
272         loadCustomConfigsForDisableReasonInfos();
273     }
274 
275     // A helper to log debugging information in the local log buffer, which can
276     // be retrieved in bugreport.
localLog(String log)277     private void localLog(String log) {
278         mLocalLog.log(log);
279     }
280 
281     /**
282      * calculates the blocklist duration based on the current failure streak with exponential
283      * backoff.
284      * @param failureStreak should be greater or equal to 0.
285      * @return duration to block the BSSID in milliseconds
286      */
getBlocklistDurationWithExponentialBackoff(int failureStreak, int baseBlocklistDurationMs)287     private long getBlocklistDurationWithExponentialBackoff(int failureStreak,
288             int baseBlocklistDurationMs) {
289         long disableDurationMs = baseBlocklistDurationMs;
290         failureStreak = Math.min(failureStreak, mContext.getResources().getInteger(
291                 R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap));
292         if (failureStreak >= 1) {
293             disableDurationMs =
294                 (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs);
295         }
296         return Math.min(disableDurationMs, mWifiGlobals.getWifiConfigMaxDisableDurationMs());
297     }
298 
299     /**
300      * Dump the local log buffer and other internal state of WifiBlocklistMonitor.
301      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)302     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
303         pw.println("Dump of WifiBlocklistMonitor");
304         mLocalLog.dump(fd, pw, args);
305         pw.println("WifiBlocklistMonitor - Bssid blocklist begin ----");
306         mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry));
307         pw.println("WifiBlocklistMonitor - Bssid blocklist end ----");
308         pw.println("Dump of BSSID to Affiliated BSSID mapping");
309         mAffiliatedBssidMap.forEach((bssid, aList) -> pw.println(bssid + " -> " + aList));
310         mBssidBlocklistMonitorLogger.dump(pw);
311     }
312 
addToBlocklist(@onNull BssidStatus entry, long durationMs, @FailureReason int reason, int rssi)313     private void addToBlocklist(@NonNull BssidStatus entry, long durationMs,
314             @FailureReason int reason, int rssi) {
315         entry.setAsBlocked(durationMs, reason, rssi);
316         localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid
317                 + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason)
318                 + ", rssi=" + rssi);
319     }
320 
321     /**
322      * increments the number of failures for the given bssid and returns the number of failures so
323      * far.
324      * @return the BssidStatus for the BSSID
325      */
incrementFailureCountForBssid( @onNull String bssid, @NonNull String ssid, int reasonCode)326     private @NonNull BssidStatus incrementFailureCountForBssid(
327             @NonNull String bssid, @NonNull String ssid, int reasonCode) {
328         BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
329         status.incrementFailureCount(reasonCode);
330         return status;
331     }
332 
333     /**
334      * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist.
335      */
getOrCreateBssidStatus(@onNull String bssid, @NonNull String ssid)336     private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid,
337             @NonNull String ssid) {
338         BssidStatus status = mBssidStatusMap.get(bssid);
339         if (status == null || !ssid.equals(status.ssid)) {
340             if (status != null) {
341                 localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from "
342                         + status.ssid + " to " + ssid);
343             }
344             status = new BssidStatus(bssid, ssid);
345             mBssidStatusMap.put(bssid, status);
346         }
347         return status;
348     }
349 
350     /**
351      * Set a list of SSIDs that will always be enabled for network selection.
352      */
setSsidsAllowlist(@onNull List<WifiSsid> ssids)353     public void setSsidsAllowlist(@NonNull List<WifiSsid> ssids) {
354         mSsidsAllowlistForNetworkSelection = new ArraySet<>(ssids);
355     }
356 
357     /**
358      * Get the list of SSIDs that will always be enabled for network selection.
359      */
getSsidsAllowlist()360     public List<WifiSsid> getSsidsAllowlist() {
361         return new ArrayList<>(mSsidsAllowlistForNetworkSelection);
362     }
363 
isValidNetworkAndFailureReasonForBssidBlocking(String bssid, WifiConfiguration config, @FailureReason int reasonCode)364     private boolean isValidNetworkAndFailureReasonForBssidBlocking(String bssid,
365             WifiConfiguration config, @FailureReason int reasonCode) {
366         if (bssid == null || config == null
367                 || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
368                 || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) {
369             Log.e(TAG, "Invalid input: BSSID=" + bssid + ", config=" + config
370                     + ", reasonCode=" + reasonCode);
371             return false;
372         }
373         return !isConfigExemptFromBlocklist(config);
374     }
375 
isConfigExemptFromBlocklist(@onNull WifiConfiguration config)376     private boolean isConfigExemptFromBlocklist(@NonNull WifiConfiguration config) {
377         try {
378             // Only enterprise owned configs that are in the doNoBlocklist are exempt from
379             // blocklisting.
380             WifiSsid wifiSsid = WifiSsid.fromString(config.SSID);
381             return mSsidsAllowlistForNetworkSelection.contains(wifiSsid)
382                     && mWifiPermissionsUtil.isAdmin(config.creatorUid, config.creatorName);
383         } catch (IllegalArgumentException e) {
384             Log.e(TAG, "Failed to convert raw ssid=" + config.SSID + " to WifiSsid");
385             return false;
386         }
387     }
388 
shouldWaitForWatchdogToTriggerFirst(String bssid, @FailureReason int reasonCode)389     private boolean shouldWaitForWatchdogToTriggerFirst(String bssid,
390             @FailureReason int reasonCode) {
391         boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION
392                 || reasonCode == REASON_AUTHENTICATION_FAILURE
393                 || reasonCode == REASON_DHCP_FAILURE;
394         return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid);
395     }
396 
397     /**
398      * Block any attempts to auto-connect to the BSSID for the specified duration.
399      * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration,
400      * and thus will not increase the failure streak counters.
401      * @param bssid identifies the AP to block.
402      * @param config identifies the WifiConfiguration.
403      * @param durationMs duration in millis to block.
404      * @param blockReason reason for blocking the BSSID.
405      * @param rssi the latest RSSI observed.
406      */
blockBssidForDurationMs(@onNull String bssid, WifiConfiguration config, long durationMs, @FailureReason int blockReason, int rssi)407     public void blockBssidForDurationMs(@NonNull String bssid, WifiConfiguration config,
408             long durationMs, @FailureReason int blockReason, int rssi) {
409         if (durationMs <= 0 || !isValidNetworkAndFailureReasonForBssidBlocking(
410                 bssid, config, blockReason)) {
411             Log.e(TAG, "Invalid input: BSSID=" + bssid + ", config=" + config
412                     + ", durationMs=" + durationMs + ", blockReason=" + blockReason
413                     + ", rssi=" + rssi);
414             return;
415         }
416         BssidStatus status = getOrCreateBssidStatus(bssid, config.SSID);
417         if (status.isInBlocklist
418                 && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) {
419             // Return because this BSSID is already being blocked for a longer time.
420             return;
421         }
422         addToBlocklist(status, durationMs, blockReason, rssi);
423         /**
424          * Add affiliated BSSIDs also into the block list with the same parameters as connected
425          * BSSID.
426          */
427         for (String affiliatedBssid : getAffiliatedBssids(bssid)) {
428             status = getOrCreateBssidStatus(affiliatedBssid, config.SSID);
429             addToBlocklist(status, durationMs, blockReason, rssi);
430         }
431     }
432 
433     /**
434      * Clear the blocklisted bssid entries with a specific block reason.
435      * @param blockReason block reason from WifiBlocklistMonitor.REASON_*
436      */
clearBssidBlocklistForReason(@ailureReason int blockReason)437     public void clearBssidBlocklistForReason(@FailureReason int blockReason) {
438         mBssidStatusMap.entrySet().removeIf(entry -> entry.getValue().blockReason == blockReason);
439     }
440 
getFailureReasonString(@ailureReason int reasonCode)441     private String getFailureReasonString(@FailureReason int reasonCode) {
442         if (reasonCode == INVALID_REASON) {
443             return "INVALID_REASON";
444         }
445         BssidDisableReason disableReason = mBssidDisableReasons.get(reasonCode);
446         if (disableReason == null) {
447             return "REASON_UNKNOWN";
448         }
449         return disableReason.reasonString;
450     }
451 
getFailureThresholdForReason(@ailureReason int reasonCode)452     private int getFailureThresholdForReason(@FailureReason int reasonCode) {
453         if (mFailureCountDisableThresholdArrayInitialized) {
454             return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
455         }
456         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] =
457                 mContext.getResources().getInteger(
458                         R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold);
459         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] =
460                 mContext.getResources().getInteger(R.integer
461                         .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold);
462         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] =
463                 mContext.getResources().getInteger(
464                         R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold);
465         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] =
466                 mContext.getResources().getInteger(
467                         R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold);
468         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] =
469                 mContext.getResources().getInteger(
470                         R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold);
471         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] =
472                 mContext.getResources().getInteger(
473                         R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold);
474         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] =
475                 mContext.getResources().getInteger(
476                         R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold);
477         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] =
478                 mContext.getResources().getInteger(
479                         R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold);
480         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] =
481                 mContext.getResources().getInteger(
482                         R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold);
483         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NONLOCAL_DISCONNECT_CONNECTING] =
484                 mContext.getResources().getInteger(R.integer
485                         .config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold);
486         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_FAILURE_NO_RESPONSE] =
487                 mContext.getResources().getInteger(R.integer
488                         .config_wifiBssidBlocklistMonitorNoResponseThreshold);
489         mFailureCountDisableThresholdArrayInitialized = true;
490         return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
491     }
492 
handleBssidConnectionFailureInternal(String bssid, String ssid, @FailureReason int reasonCode, int rssi)493     private boolean handleBssidConnectionFailureInternal(String bssid, String ssid,
494             @FailureReason int reasonCode, int rssi) {
495         BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode);
496         int failureThreshold = getFailureThresholdForReason(reasonCode);
497         int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode);
498         if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) {
499             // To rule out potential device side issues, don't add to blocklist if
500             // WifiLastResortWatchdog is still not triggered
501             if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) {
502                 localLog("Ignoring failure to wait for watchdog to trigger first.");
503                 return false;
504             }
505             // rssi may be unavailable for the first ever connection to a newly added network
506             // because it hasn't been cached inside the ScanDetailsCache yet. In this case, try to
507             // read the RSSI from the latest scan results.
508             if (rssi == WifiConfiguration.INVALID_RSSI && bssid != null) {
509                 if (mScanRequestProxy != null) {
510                     ScanResult scanResult = mScanRequestProxy.getScanResult(bssid);
511                     if (scanResult != null) {
512                         rssi = scanResult.level;
513                     }
514                 } else {
515                     localLog("mScanRequestProxy is null");
516                     Log.w(TAG, "mScanRequestProxy is null");
517                 }
518             }
519             int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode);
520             long expBackoff = getBlocklistDurationWithExponentialBackoff(currentStreak,
521                     baseBlockDurationMs);
522             addToBlocklist(entry, expBackoff, reasonCode, rssi);
523             mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode);
524 
525             /**
526              * Block list affiliated BSSID with same parameters, e.g. reason code, rssi ..etc.
527              * as connected BSSID.
528              */
529             for (String affiliatedBssid : getAffiliatedBssids(bssid)) {
530                 BssidStatus affEntry = getOrCreateBssidStatus(affiliatedBssid, ssid);
531                 affEntry.failureCount[reasonCode] = entry.failureCount[reasonCode];
532                 addToBlocklist(affEntry, expBackoff, reasonCode, rssi);
533                 mWifiScoreCard.incrementBssidBlocklistStreak(ssid, affiliatedBssid, reasonCode);
534             }
535 
536             return true;
537         }
538         return false;
539     }
540 
getBaseBlockDurationForReason(int blockReason)541     private int getBaseBlockDurationForReason(int blockReason) {
542         switch (blockReason) {
543             case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
544                 return mContext.getResources().getInteger(R.integer
545                         .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs);
546             case REASON_NETWORK_VALIDATION_FAILURE:
547                 return mContext.getResources().getInteger(
548                         R.integer.config_wifiBssidBlocklistMonitorValidationFailureBaseBlockDurationMs);
549             default:
550                 return mContext.getResources().getInteger(
551                     R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs);
552         }
553     }
554 
555     /**
556      * Note a failure event on a bssid and perform appropriate actions.
557      * @return True if the blocklist has been modified.
558      */
handleBssidConnectionFailure(String bssid, WifiConfiguration config, @FailureReason int reasonCode, int rssi)559     public boolean handleBssidConnectionFailure(String bssid, WifiConfiguration config,
560             @FailureReason int reasonCode, int rssi) {
561         if (!isValidNetworkAndFailureReasonForBssidBlocking(bssid, config, reasonCode)) {
562             return false;
563         }
564         String ssid = config.SSID;
565         BssidDisableReason bssidDisableReason = mBssidDisableReasons.get(reasonCode);
566         if (bssidDisableReason == null) {
567             Log.e(TAG, "Bssid disable reason not found. ReasonCode=" + reasonCode);
568             return false;
569         }
570         if (bssidDisableReason.ignoreIfOnlyBssid && !mDisabledSsids.contains(ssid)
571                 && mWifiLastResortWatchdog.isBssidOnlyApOfSsid(bssid)) {
572             localLog("Ignoring BSSID failure due to no other APs available. BSSID=" + bssid);
573             return false;
574         }
575         if (reasonCode == REASON_ABNORMAL_DISCONNECT) {
576             long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid);
577             // only count disconnects that happen shortly after a connection.
578             if (mClock.getWallClockMillis() - connectionTime
579                     > mContext.getResources().getInteger(
580                             R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) {
581                 return false;
582             }
583         }
584         return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi);
585     }
586 
587     /**
588      * To be called when a WifiConfiguration is either temporarily disabled or permanently disabled.
589      * @param ssid of the WifiConfiguration that is disabled.
590      */
handleWifiConfigurationDisabled(String ssid)591     public void handleWifiConfigurationDisabled(String ssid) {
592         if (ssid != null) {
593             mDisabledSsids.add(ssid);
594         }
595     }
596 
597     /**
598      * Note a connection success event on a bssid and clear appropriate failure counters.
599      */
handleBssidConnectionSuccess(@onNull String bssid, @NonNull String ssid)600     public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) {
601         mDisabledSsids.remove(ssid);
602         resetFailuresAfterConnection(bssid, ssid);
603         for (String affiliatedBssid : getAffiliatedBssids(bssid)) {
604             resetFailuresAfterConnection(affiliatedBssid, ssid);
605         }
606     }
607 
608     /**
609      * Reset all failure counters related to a connection.
610      *
611      * @param bssid A unique identifier of the AP.
612      * @param ssid Network name.
613      */
resetFailuresAfterConnection(@onNull String bssid, @NonNull String ssid)614     private void resetFailuresAfterConnection(@NonNull String bssid, @NonNull String ssid) {
615 
616         /**
617          * First reset the blocklist streak.
618          * This needs to be done even if a BssidStatus is not found, since the BssidStatus may
619          * have been removed due to blocklist timeout.
620          */
621         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
622         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD);
623         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE);
624         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION);
625         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT);
626         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE);
627         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
628                 REASON_NONLOCAL_DISCONNECT_CONNECTING);
629         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_FAILURE_NO_RESPONSE);
630 
631         long connectionTime = mClock.getWallClockMillis();
632         long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs(
633                 ssid, bssid, connectionTime);
634         if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
635             mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT);
636             mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
637                     REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
638         }
639 
640         BssidStatus status = mBssidStatusMap.get(bssid);
641         if (status == null) {
642             return;
643         }
644         // Clear the L2 failure counters
645         status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0;
646         status.failureCount[REASON_WRONG_PASSWORD] = 0;
647         status.failureCount[REASON_EAP_FAILURE] = 0;
648         status.failureCount[REASON_ASSOCIATION_REJECTION] = 0;
649         status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0;
650         status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0;
651         status.failureCount[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 0;
652         status.failureCount[REASON_FAILURE_NO_RESPONSE] = 0;
653         if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
654             status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0;
655         }
656     }
657 
658     /**
659      * Note a successful network validation on a BSSID and clear appropriate failure counters.
660      * And then remove the BSSID from blocklist.
661      */
handleNetworkValidationSuccess(@onNull String bssid, @NonNull String ssid)662     public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) {
663         resetNetworkValidationFailures(bssid, ssid);
664         /**
665          * Network validation may take more than 1 tries to succeed.
666          * remove the BSSID from blocklist to make sure we are not accidentally blocking good
667          * BSSIDs.
668          **/
669         removeFromBlocklist(bssid, "Network validation success");
670 
671         for (String affiliatedBssid : getAffiliatedBssids(bssid)) {
672             resetNetworkValidationFailures(affiliatedBssid, ssid);
673             removeFromBlocklist(affiliatedBssid, "Network validation success");
674         }
675     }
676 
677     /**
678      * Clear failure counters related to network validation.
679      *
680      * @param bssid A unique identifier of the AP.
681      * @param ssid Network name.
682      */
resetNetworkValidationFailures(@onNull String bssid, @NonNull String ssid)683     private void resetNetworkValidationFailures(@NonNull String bssid, @NonNull String ssid) {
684         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE);
685         BssidStatus status = mBssidStatusMap.get(bssid);
686         if (status == null) {
687             return;
688         }
689         status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0;
690     }
691 
692     /**
693      * Remove BSSID from block list.
694      *
695      * @param bssid A unique identifier of the AP.
696      * @param reasonString A string to be logged while removing the entry from the block list.
697      */
removeFromBlocklist(@onNull String bssid, final String reasonString)698     private void removeFromBlocklist(@NonNull String bssid, final String reasonString) {
699         BssidStatus status = mBssidStatusMap.get(bssid);
700         if (status == null) {
701             return;
702         }
703 
704         if (status.isInBlocklist) {
705             mBssidBlocklistMonitorLogger.logBssidUnblocked(status, reasonString);
706             mBssidStatusMap.remove(bssid);
707         }
708     }
709 
710     /**
711      * Note a successful DHCP provisioning and clear appropriate failure counters.
712      */
handleDhcpProvisioningSuccess(@onNull String bssid, @NonNull String ssid)713     public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) {
714         resetDhcpFailures(bssid, ssid);
715         for (String affiliatedBssid : getAffiliatedBssids(bssid)) {
716             resetDhcpFailures(affiliatedBssid, ssid);
717         }
718     }
719 
720     /**
721      * Reset failure counters related to DHCP.
722      */
resetDhcpFailures(@onNull String bssid, @NonNull String ssid)723     private void resetDhcpFailures(@NonNull String bssid, @NonNull String ssid) {
724         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE);
725         BssidStatus status = mBssidStatusMap.get(bssid);
726         if (status == null) {
727             return;
728         }
729         status.failureCount[REASON_DHCP_FAILURE] = 0;
730     }
731 
732     /**
733      * Note the removal of a network from the Wifi stack's internal database and reset
734      * appropriate failure counters.
735      * @param ssid
736      */
handleNetworkRemoved(@onNull String ssid)737     public void handleNetworkRemoved(@NonNull String ssid) {
738         clearBssidBlocklistForSsid(ssid);
739         mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid);
740     }
741 
742     /**
743      * Clears the blocklist for BSSIDs associated with the input SSID only.
744      * @param ssid
745      */
746     @Keep
clearBssidBlocklistForSsid(@onNull String ssid)747     public void clearBssidBlocklistForSsid(@NonNull String ssid) {
748         int prevSize = mBssidStatusMap.size();
749         mBssidStatusMap.entrySet().removeIf(e -> {
750             BssidStatus status = e.getValue();
751             if (status.ssid == null) {
752                 return false;
753             }
754             if (status.ssid.equals(ssid)) {
755                 mBssidBlocklistMonitorLogger.logBssidUnblocked(
756                         status, "clearBssidBlocklistForSsid");
757                 return true;
758             }
759             return false;
760         });
761         int diff = prevSize - mBssidStatusMap.size();
762         if (diff > 0) {
763             localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid
764                     + ", num BSSIDs cleared=" + diff);
765         }
766     }
767 
768     /**
769      * Clears the BSSID blocklist and failure counters.
770      */
clearBssidBlocklist()771     public void clearBssidBlocklist() {
772         if (mBssidStatusMap.size() > 0) {
773             int prevSize = mBssidStatusMap.size();
774             for (BssidStatus status : mBssidStatusMap.values()) {
775                 mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "clearBssidBlocklist");
776             }
777             mBssidStatusMap.clear();
778             localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared="
779                     + (prevSize - mBssidStatusMap.size()));
780         }
781         mDisabledSsids.clear();
782     }
783 
784     /**
785      * @param ssid
786      * @return the number of BSSIDs currently in the blocklist for the |ssid|.
787      */
updateAndGetNumBlockedBssidsForSsid(@onNull String ssid)788     public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) {
789         return (int) updateAndGetBssidBlocklistInternal()
790                 .filter(entry -> ssid.equals(entry.ssid)).count();
791     }
792 
getNumBlockedBssidsForSsids(@onNull Set<String> ssids)793     private int getNumBlockedBssidsForSsids(@NonNull Set<String> ssids) {
794         if (ssids.isEmpty()) {
795             return 0;
796         }
797         return (int) mBssidStatusMap.values().stream()
798                 .filter(entry -> entry.isInBlocklist && ssids.contains(entry.ssid))
799                 .count();
800     }
801 
802     /**
803      * Overloaded version of updateAndGetBssidBlocklist.
804      * Accepts a @Nullable String ssid as input, and updates the firmware roaming
805      * configuration if the blocklist for the input ssid has been changed.
806      * @param ssids set of ssids to update firmware roaming configuration for.
807      * @return Set of BSSIDs currently in the blocklist
808      */
updateAndGetBssidBlocklistForSsids(@onNull Set<String> ssids)809     public Set<String> updateAndGetBssidBlocklistForSsids(@NonNull Set<String> ssids) {
810         int numBefore = getNumBlockedBssidsForSsids(ssids);
811         Set<String> bssidBlocklist = updateAndGetBssidBlocklist();
812         if (getNumBlockedBssidsForSsids(ssids) != numBefore) {
813             updateFirmwareRoamingConfiguration(ssids);
814         }
815         return bssidBlocklist;
816     }
817 
818     /**
819      * Gets the BSSIDs that are currently in the blocklist.
820      * @return Set of BSSIDs currently in the blocklist
821      */
updateAndGetBssidBlocklist()822     public Set<String> updateAndGetBssidBlocklist() {
823         return updateAndGetBssidBlocklistInternal()
824                 .map(entry -> entry.bssid)
825                 .collect(Collectors.toSet());
826     }
827 
828     /**
829      * Gets the list of block reasons for BSSIDs currently in the blocklist.
830      * @return The set of unique reasons for blocking BSSIDs with this SSID.
831      */
getFailureReasonsForSsid(@onNull String ssid)832     public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) {
833         if (ssid == null) {
834             return Collections.emptySet();
835         }
836         return mBssidStatusMap.values().stream()
837                 .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
838                 .map(entry -> entry.blockReason)
839                 .collect(Collectors.toSet());
840     }
841 
842     /**
843      * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI.
844      * @param scanDetails
845      * @return the list of ScanDetails for which BSSIDs were re-enabled.
846      */
tryEnablingBlockedBssids(List<ScanDetail> scanDetails)847     public @NonNull List<ScanDetail> tryEnablingBlockedBssids(List<ScanDetail> scanDetails) {
848         if (scanDetails == null) {
849             return Collections.EMPTY_LIST;
850         }
851         List<ScanDetail> results = new ArrayList<>();
852         for (ScanDetail scanDetail : scanDetails) {
853             ScanResult scanResult = scanDetail.getScanResult();
854             if (scanResult == null) {
855                 continue;
856             }
857             BssidStatus status = mBssidStatusMap.get(scanResult.BSSID);
858             if (status == null || !status.isInBlocklist
859                     || !isLowRssiSensitiveFailure(status.blockReason)) {
860                 continue;
861             }
862             int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency);
863             int goodRssi = mScoringParams.getGoodRssi(scanResult.frequency);
864             boolean rssiMinDiffAchieved = scanResult.level - status.lastRssi
865                     >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID;
866             boolean sufficientRssiBreached =
867                     status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi;
868             boolean goodRssiBreached = status.lastRssi < goodRssi && scanResult.level >= goodRssi;
869             if (rssiMinDiffAchieved && (sufficientRssiBreached || goodRssiBreached)) {
870                 removeFromBlocklist(status.bssid, "rssi significantly improved");
871                 for (String affiliatedBssid : getAffiliatedBssids(status.bssid)) {
872                     removeFromBlocklist(affiliatedBssid, "rssi significantly improved");
873                 }
874                 results.add(scanDetail);
875             }
876         }
877         return results;
878     }
879 
isLowRssiSensitiveFailure(int blockReason)880     private boolean isLowRssiSensitiveFailure(int blockReason) {
881         return mBssidDisableReasons.get(blockReason) == null ? false
882                 : mBssidDisableReasons.get(blockReason).isLowRssiSensitive;
883     }
884 
885     /**
886      * Removes expired BssidStatus entries and then return remaining entries in the blocklist.
887      * @return Stream of BssidStatus for BSSIDs that are in the blocklist.
888      */
updateAndGetBssidBlocklistInternal()889     private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() {
890         Stream.Builder<BssidStatus> builder = Stream.builder();
891         long curTime = mClock.getWallClockMillis();
892         mBssidStatusMap.entrySet().removeIf(e -> {
893             BssidStatus status = e.getValue();
894             if (status.isInBlocklist) {
895                 if (status.blocklistEndTimeMs < curTime) {
896                     mBssidBlocklistMonitorLogger.logBssidUnblocked(
897                             status, "updateAndGetBssidBlocklistInternal");
898                     return true;
899                 }
900                 builder.accept(status);
901             }
902             return false;
903         });
904         return builder.build();
905     }
906 
907     /**
908      * Gets the currently blocked BSSIDs without causing any updates.
909      * @param ssids The set of SSIDs to get blocked BSSID for, or null to get this information for
910      *              all SSIDs.
911      * @return The list of currently blocked BSSIDs.
912      */
getBssidBlocklistForSsids(@ullable Set<String> ssids)913     public List<String> getBssidBlocklistForSsids(@Nullable Set<String> ssids) {
914         List<String> results = new ArrayList<>();
915         for (Map.Entry<String, BssidStatus> entryMap : mBssidStatusMap.entrySet()) {
916             BssidStatus bssidStatus = entryMap.getValue();
917             if (bssidStatus.isInBlocklist && (ssids == null || ssids.contains(bssidStatus.ssid))) {
918                 results.add(bssidStatus.bssid);
919             }
920         }
921         return results;
922     }
923 
924     /**
925      * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming
926      * to those BSSIDs.
927      * @param ssids
928      */
updateFirmwareRoamingConfiguration(@onNull Set<String> ssids)929     public void updateFirmwareRoamingConfiguration(@NonNull Set<String> ssids) {
930         if (!mConnectivityHelper.isFirmwareRoamingSupported()) {
931             return;
932         }
933         ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal()
934                 .filter(entry -> ssids.contains(entry.ssid))
935                 .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs))
936                 .map(entry -> entry.bssid)
937                 .collect(Collectors.toCollection(ArrayList::new));
938         int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlocklistBssid();
939         if (fwMaxBlocklistSize <= 0) {
940             Log.e(TAG, "Invalid max BSSID blocklist size:  " + fwMaxBlocklistSize);
941             return;
942         }
943         // Having the blocklist size exceeding firmware max limit is unlikely because we have
944         // already flitered based on SSID. But just in case this happens, we are prioritizing
945         // sending down BSSIDs blocked for the longest time.
946         if (bssidBlocklist.size() > fwMaxBlocklistSize) {
947             bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0,
948                     fwMaxBlocklistSize));
949         }
950 
951         // Collect all the allowed SSIDs
952         Set<String> allowedSsidSet = new HashSet<>();
953         for (String ssid : ssids) {
954             List<String> allowedSsidsForSsid = mSsidAllowlistMap.get(ssid);
955             if (allowedSsidsForSsid != null) {
956                 allowedSsidSet.addAll(allowedSsidsForSsid);
957             }
958         }
959         ArrayList<String> ssidAllowlist = new ArrayList<>(allowedSsidSet);
960         int allowlistSize = ssidAllowlist.size();
961         int maxAllowlistSize = mConnectivityHelper.getMaxNumAllowlistSsid();
962         if (maxAllowlistSize <= 0) {
963             Log.wtf(TAG, "Invalid max SSID allowlist size:  " + maxAllowlistSize);
964             return;
965         }
966         if (allowlistSize > maxAllowlistSize) {
967             ssidAllowlist = new ArrayList<>(ssidAllowlist.subList(0, maxAllowlistSize));
968             localLog("Trim down SSID allowlist size from " + allowlistSize + " to "
969                     + ssidAllowlist.size());
970         }
971 
972         // plumb down to HAL
973         String message = "set firmware roaming configurations. "
974                 + "bssidBlocklist=";
975         if (bssidBlocklist.size() == 0) {
976             message += "<EMPTY>";
977         } else {
978             message += String.join(", ", bssidBlocklist);
979         }
980         if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, ssidAllowlist)) {
981             Log.e(TAG, "Failed to " + message);
982             mBssidBlocklistMonitorLogger.log("Failed to " + message);
983         } else {
984             mBssidBlocklistMonitorLogger.log("Successfully " + message);
985         }
986     }
987 
988     @VisibleForTesting
getBssidBlocklistMonitorLoggerSize()989     public int getBssidBlocklistMonitorLoggerSize() {
990         return mBssidBlocklistMonitorLogger.size();
991     }
992 
993     private class BssidBlocklistMonitorLogger {
994         private LinkedList<String> mLogBuffer = new LinkedList<>();
995         private int mBufferSize;
996 
BssidBlocklistMonitorLogger(int bufferSize)997         BssidBlocklistMonitorLogger(int bufferSize) {
998             mBufferSize = bufferSize;
999         }
1000 
logBssidUnblocked(BssidStatus bssidStatus, String unblockReason)1001         public void logBssidUnblocked(BssidStatus bssidStatus, String unblockReason) {
1002             // only log history for Bssids that had been blocked.
1003             if (bssidStatus == null || !bssidStatus.isInBlocklist) {
1004                 return;
1005             }
1006             StringBuilder sb = createStringBuilderWithLogTime();
1007             sb.append(", Bssid unblocked, Reason=" + unblockReason);
1008             sb.append(", Unblocked BssidStatus={" + bssidStatus.toString() + "}");
1009             logInternal(sb.toString());
1010         }
1011 
1012         // cache a single line of log message in the rotating buffer
log(String message)1013         public void log(String message) {
1014             if (message == null) {
1015                 return;
1016             }
1017             StringBuilder sb = createStringBuilderWithLogTime();
1018             sb.append(" " + message);
1019             logInternal(sb.toString());
1020         }
1021 
createStringBuilderWithLogTime()1022         private StringBuilder createStringBuilderWithLogTime() {
1023             StringBuilder sb = new StringBuilder();
1024             Calendar c = Calendar.getInstance();
1025             c.setTimeInMillis(mClock.getWallClockMillis());
1026             sb.append("logTime=").append(StringUtil.calendarToString(c));
1027             return sb;
1028         }
1029 
logInternal(String message)1030         private void logInternal(String message) {
1031             mLogBuffer.add(message);
1032             if (mLogBuffer.size() > mBufferSize) {
1033                 mLogBuffer.removeFirst();
1034             }
1035         }
1036 
1037         @VisibleForTesting
size()1038         public int size() {
1039             return mLogBuffer.size();
1040         }
1041 
dump(PrintWriter pw)1042         public void dump(PrintWriter pw) {
1043             pw.println("WifiBlocklistMonitor - Bssid blocklist logs begin ----");
1044             for (String line : mLogBuffer) {
1045                 pw.println(line);
1046             }
1047             pw.println("List of SSIDs to never block:");
1048             for (WifiSsid ssid : mSsidsAllowlistForNetworkSelection) {
1049                 pw.println(ssid.toString());
1050             }
1051             pw.println("WifiBlocklistMonitor - Bssid blocklist logs end ----");
1052         }
1053     }
1054 
1055     /**
1056      * Helper class that counts the number of failures per BSSID.
1057      */
1058     private class BssidStatus {
1059         public final String bssid;
1060         public final String ssid;
1061         public final int[] failureCount = new int[NUMBER_REASON_CODES];
1062         public int blockReason = INVALID_REASON; // reason of blocking this BSSID
1063         // The latest RSSI that's seen before this BSSID is added to blocklist.
1064         public int lastRssi = 0;
1065 
1066         // The following are used to flag how long this BSSID stays in the blocklist.
1067         public boolean isInBlocklist;
1068         public long blocklistEndTimeMs;
1069         public long blocklistStartTimeMs;
1070 
BssidStatus(String bssid, String ssid)1071         BssidStatus(String bssid, String ssid) {
1072             this.bssid = bssid;
1073             this.ssid = ssid;
1074         }
1075 
1076         /**
1077          * increments the failure count for the reasonCode by 1.
1078          * @return the incremented failure count
1079          */
incrementFailureCount(int reasonCode)1080         public int incrementFailureCount(int reasonCode) {
1081             return ++failureCount[reasonCode];
1082         }
1083 
1084         /**
1085          * Set this BSSID as blocked for the specified duration.
1086          * @param durationMs
1087          * @param blockReason
1088          * @param rssi
1089          */
setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi)1090         public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) {
1091             isInBlocklist = true;
1092             blocklistStartTimeMs = mClock.getWallClockMillis();
1093             blocklistEndTimeMs = blocklistStartTimeMs + durationMs;
1094             this.blockReason = blockReason;
1095             lastRssi = rssi;
1096             mWifiMetrics.incrementBssidBlocklistCount(blockReason);
1097         }
1098 
1099         @Override
toString()1100         public String toString() {
1101             StringBuilder sb = new StringBuilder();
1102             sb.append("BSSID=" + bssid);
1103             sb.append(", SSID=" + ssid);
1104             sb.append(", isInBlocklist=" + isInBlocklist);
1105             if (isInBlocklist) {
1106                 sb.append(", blockReason=" + getFailureReasonString(blockReason));
1107                 sb.append(", lastRssi=" + lastRssi);
1108                 Calendar c = Calendar.getInstance();
1109                 c.setTimeInMillis(blocklistStartTimeMs);
1110                 sb.append(", blocklistStartTime=").append(StringUtil.calendarToString(c));
1111                 c.setTimeInMillis(blocklistEndTimeMs);
1112                 sb.append(", blocklistEndTime=").append(StringUtil.calendarToString(c));
1113             }
1114             return sb.toString();
1115         }
1116     }
1117 
1118     /**
1119      * Enable/disable verbose logging in WifiBlocklistMonitor.
1120      */
enableVerboseLogging(boolean verbose)1121     public void enableVerboseLogging(boolean verbose) {
1122         mVerboseLoggingEnabled = verbose;
1123     }
1124 
1125     /**
1126      * Modify the internal copy of DisableReasonInfo with custom configurations defined in
1127      * an overlay.
1128      */
loadCustomConfigsForDisableReasonInfos()1129     private void loadCustomConfigsForDisableReasonInfos() {
1130         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
1131                 new DisableReasonInfo(
1132                         // Note that there is a space at the end of this string. Cannot fix
1133                         // since this string is persisted.
1134                         "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
1135                         mContext.getResources().getInteger(R.integer
1136                                 .config_wifiDisableReasonAssociationRejectionThreshold),
1137                         mContext.getResources().getInteger(R.integer
1138                                 .config_wifiDisableReasonAssociationRejectionDurationMs)));
1139 
1140         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE,
1141                 new DisableReasonInfo(
1142                         "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
1143                         mContext.getResources().getInteger(R.integer
1144                                 .config_wifiDisableReasonAuthenticationFailureThreshold),
1145                         mContext.getResources().getInteger(R.integer
1146                                 .config_wifiDisableReasonAuthenticationFailureDurationMs)));
1147 
1148         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_DHCP_FAILURE,
1149                 new DisableReasonInfo(
1150                         "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
1151                         mContext.getResources().getInteger(R.integer
1152                                 .config_wifiDisableReasonDhcpFailureThreshold),
1153                         mContext.getResources().getInteger(R.integer
1154                                 .config_wifiDisableReasonDhcpFailureDurationMs)));
1155 
1156         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND,
1157                 new DisableReasonInfo(
1158                         "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND",
1159                         mContext.getResources().getInteger(R.integer
1160                                 .config_wifiDisableReasonNetworkNotFoundThreshold),
1161                         mContext.getResources().getInteger(R.integer
1162                                 .config_wifiDisableReasonNetworkNotFoundDurationMs)));
1163 
1164         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY,
1165                 new DisableReasonInfo(
1166                 "NETWORK_SELECTION_DISABLED_NO_INTERNET_TEMPORARY",
1167                 mContext.getResources().getInteger(R.integer
1168                     .config_wifiDisableReasonNoInternetTemporaryThreshold),
1169                 mContext.getResources().getInteger(R.integer
1170                     .config_wifiDisableReasonNoInternetTemporaryDurationMs)));
1171 
1172         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_CREDENTIALS,
1173                 new DisableReasonInfo(
1174                         "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
1175                         mContext.getResources().getInteger(R.integer
1176                                 .config_wifiDisableReasonAuthenticationNoCredentialsThreshold),
1177                         mContext.getResources().getInteger(R.integer
1178                                 .config_wifiDisableReasonAuthenticationNoCredentialsDurationMs)));
1179 
1180         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT,
1181                 new DisableReasonInfo(
1182                         "NETWORK_SELECTION_DISABLED_NO_INTERNET_PERMANENT",
1183                         mContext.getResources().getInteger(R.integer
1184                                 .config_wifiDisableReasonNoInternetPermanentThreshold),
1185                         mContext.getResources().getInteger(R.integer
1186                                 .config_wifiDisableReasonNoInternetPermanentDurationMs)));
1187 
1188         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD,
1189                 new DisableReasonInfo(
1190                         "NETWORK_SELECTION_DISABLED_BY_WRONG_PASSWORD",
1191                         mContext.getResources().getInteger(R.integer
1192                                 .config_wifiDisableReasonByWrongPasswordThreshold),
1193                         mContext.getResources().getInteger(R.integer
1194                                 .config_wifiDisableReasonByWrongPasswordDurationMs)));
1195 
1196         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_SUBSCRIPTION,
1197                 new DisableReasonInfo(
1198                         "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_SUBSCRIPTION",
1199                         mContext.getResources().getInteger(R.integer
1200                                 .config_wifiDisableReasonAuthenticationNoSubscriptionThreshold),
1201                         mContext.getResources().getInteger(R.integer
1202                                 .config_wifiDisableReasonAuthenticationNoSubscriptionDurationMs)));
1203 
1204         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_CONSECUTIVE_FAILURES,
1205                 new DisableReasonInfo(
1206                         "NETWORK_SELECTION_DISABLED_CONSECUTIVE_FAILURES",
1207                         mContext.getResources().getInteger(R.integer
1208                                 .config_wifiDisableReasonConsecutiveFailuresThreshold),
1209                         mContext.getResources().getInteger(R.integer
1210                                 .config_wifiDisableReasonConsecutiveFailuresDurationMs)));
1211     }
1212 
1213     /**
1214      * Update DisableReasonInfo with carrier configurations defined in an overlay.
1215      *
1216      * TODO(236173881): mDisableReasonInfo storing the carrier specific EAP failure threshold and
1217      * duration is always keyed by NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR.
1218      * This is error prone now that different carrier networks could have different thresholds and
1219      * durations. But with the current code only the last updated one will remain in
1220      * mDisableReasonInfo. Need to clean this up to be more robust.
1221      */
loadCarrierConfigsForDisableReasonInfos( @onNull CarrierSpecificEapFailureConfig config)1222     public void loadCarrierConfigsForDisableReasonInfos(
1223             @NonNull CarrierSpecificEapFailureConfig config) {
1224         if (config == null) {
1225             Log.e(TAG, "Unexpected null CarrierSpecificEapFailureConfig");
1226             return;
1227         }
1228         DisableReasonInfo disableReasonInfo = new DisableReasonInfo(
1229                 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR",
1230                 config.threshold, config.durationMs);
1231         mDisableReasonInfo.put(
1232                 NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR,
1233                 disableReasonInfo);
1234     }
1235 
1236     /**
1237      * Class to be used to represent blocklist behavior for a certain EAP error code.
1238      */
1239     public static class CarrierSpecificEapFailureConfig {
1240         // number of failures to disable
1241         public final int threshold;
1242         // disable duration in ms. -1 means permanent disable.
1243         public final int durationMs;
1244         public final boolean displayNotification;
CarrierSpecificEapFailureConfig(int threshold, int durationMs, boolean displayNotification)1245         public CarrierSpecificEapFailureConfig(int threshold, int durationMs,
1246                 boolean displayNotification) {
1247             this.threshold = threshold;
1248             this.durationMs = durationMs;
1249             this.displayNotification = displayNotification;
1250         }
1251 
1252         @Override
hashCode()1253         public int hashCode() {
1254             return Objects.hash(threshold, durationMs, displayNotification);
1255         }
1256 
1257         @Override
equals(Object obj)1258         public boolean equals(Object obj) {
1259             if (this == obj) {
1260                 return true;
1261             }
1262             if (!(obj instanceof CarrierSpecificEapFailureConfig)) {
1263                 return false;
1264             }
1265             CarrierSpecificEapFailureConfig lhs = (CarrierSpecificEapFailureConfig) obj;
1266             return threshold == lhs.threshold && durationMs == lhs.durationMs
1267                     && displayNotification == lhs.displayNotification;
1268         }
1269 
1270         @Override
toString()1271         public String toString() {
1272             return new StringBuilder()
1273                     .append("threshold=").append(threshold)
1274                     .append(" durationMs=").append(durationMs)
1275                     .append(" displayNotification=").append(displayNotification)
1276                     .toString();
1277         }
1278     }
1279 
1280     /**
1281      * Returns true if the disable duration for this WifiConfiguration has passed. Returns false
1282      * if the WifiConfiguration is either not disabled or is permanently disabled.
1283      */
shouldEnableNetwork(WifiConfiguration config)1284     public boolean shouldEnableNetwork(WifiConfiguration config) {
1285         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1286         if (networkStatus.isNetworkTemporaryDisabled()) {
1287             return mClock.getElapsedSinceBootMillis() >= networkStatus.getDisableEndTime();
1288         }
1289         return false;
1290     }
1291 
1292     /**
1293      * Update a network's status (both internal and public) according to the update reason and
1294      * its current state. This method is expects to directly modify the internal WifiConfiguration
1295      * that is stored by WifiConfigManager.
1296      *
1297      * @param config the internal WifiConfiguration to be updated.
1298      * @param reason reason code for update.
1299      * @return true if the input configuration has been updated, false otherwise.
1300      */
updateNetworkSelectionStatus(WifiConfiguration config, int reason)1301     public boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
1302         if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
1303             Log.e(TAG, "Invalid Network disable reason " + reason);
1304             return false;
1305         }
1306         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1307         if (reason != NetworkSelectionStatus.DISABLED_NONE) {
1308             // Do not disable if in the exception list
1309             if (reason != NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER
1310                     && isConfigExemptFromBlocklist(config)) {
1311                 return false;
1312             }
1313 
1314             networkStatus.incrementDisableReasonCounter(reason);
1315             // For network disable reasons, we should only update the status if we cross the
1316             // threshold.
1317             int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
1318             int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason);
1319 
1320             // If this is the only SSID be observed, allow more failures to allow Watchdog to
1321             // trigger easier
1322             if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION
1323                     || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE
1324                     || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) {
1325                 if (mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()
1326                         && disableReasonCounter
1327                         < NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF) {
1328                     if (mVerboseLoggingEnabled) {
1329                         Log.v(TAG, "Ignore update network selection status "
1330                                 + "since Watchdog trigger is activated");
1331                     }
1332                     return false;
1333                 }
1334             }
1335 
1336             if (disableReasonCounter < disableReasonThreshold) {
1337                 if (mVerboseLoggingEnabled) {
1338                     Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
1339                             + " for reason "
1340                             + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason)
1341                             + " is " + networkStatus.getDisableReasonCounter(reason)
1342                             + " and threshold is " + disableReasonThreshold);
1343                 }
1344                 return true;
1345             }
1346         }
1347         setNetworkSelectionStatus(config, reason);
1348         return true;
1349     }
1350 
1351     /**
1352      * Sets a network's status (both internal and public) according to the update reason and
1353      * its current state.
1354      *
1355      * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
1356      * public {@link WifiConfiguration#status} field if the network is either enabled or
1357      * permanently disabled.
1358      *
1359      * @param config network to be updated.
1360      * @param reason reason code for update.
1361      */
setNetworkSelectionStatus(WifiConfiguration config, int reason)1362     private void setNetworkSelectionStatus(WifiConfiguration config, int reason) {
1363         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1364         if (reason == NetworkSelectionStatus.DISABLED_NONE) {
1365             setNetworkSelectionEnabled(config);
1366         } else if (getNetworkSelectionDisableTimeoutMillis(reason)
1367                 != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) {
1368             setNetworkSelectionTemporarilyDisabled(config, reason);
1369         } else {
1370             setNetworkSelectionPermanentlyDisabled(config, reason);
1371         }
1372         localLog("setNetworkSelectionStatus: configKey=" + config.getProfileKey()
1373                 + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
1374                 + networkStatus.getNetworkSelectionDisableReasonString());
1375     }
1376 
1377     /**
1378      * Helper method to mark a network enabled for network selection.
1379      */
setNetworkSelectionEnabled(WifiConfiguration config)1380     private void setNetworkSelectionEnabled(WifiConfiguration config) {
1381         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1382         if (status.getNetworkSelectionStatus()
1383                 != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) {
1384             localLog("setNetworkSelectionEnabled: configKey=" + config.getProfileKey()
1385                     + " old networkStatus=" + status.getNetworkStatusString()
1386                     + " disableReason=" + status.getNetworkSelectionDisableReasonString());
1387         }
1388         status.setNetworkSelectionStatus(
1389                 NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
1390         status.setDisableTime(
1391                 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1392         status.setDisableEndTime(
1393                 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1394         status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE);
1395 
1396         // Clear out all the disable reason counters.
1397         status.clearDisableReasonCounter();
1398         if (config.status == WifiConfiguration.Status.DISABLED) {
1399             config.status = WifiConfiguration.Status.ENABLED;
1400         }
1401     }
1402 
1403     /**
1404      * Helper method to mark a network temporarily disabled for network selection.
1405      */
setNetworkSelectionTemporarilyDisabled( WifiConfiguration config, int disableReason)1406     private void setNetworkSelectionTemporarilyDisabled(
1407             WifiConfiguration config, int disableReason) {
1408         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1409         status.setNetworkSelectionStatus(
1410                 NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
1411         // Only need a valid time filled in for temporarily disabled networks.
1412         status.setDisableTime(mClock.getElapsedSinceBootMillis());
1413         status.setDisableEndTime(calculateDisableEndTime(config, disableReason));
1414         status.setNetworkSelectionDisableReason(disableReason);
1415         handleWifiConfigurationDisabled(config.SSID);
1416         mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
1417     }
1418 
calculateDisableEndTime(WifiConfiguration config, int disableReason)1419     private long calculateDisableEndTime(WifiConfiguration config, int disableReason) {
1420         long disableDurationMs = (long) getNetworkSelectionDisableTimeoutMillis(disableReason);
1421         int exponentialBackoffCount = mWifiScoreCard.lookupNetwork(config.SSID)
1422                 .getRecentStats().getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)
1423                 - NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF;
1424         for (int i = 0; i < exponentialBackoffCount; i++) {
1425             disableDurationMs *= 2;
1426             if (disableDurationMs > mWifiGlobals.getWifiConfigMaxDisableDurationMs()) {
1427                 disableDurationMs = mWifiGlobals.getWifiConfigMaxDisableDurationMs();
1428                 break;
1429             }
1430         }
1431         return mClock.getElapsedSinceBootMillis() + Math.min(
1432             disableDurationMs, mWifiGlobals.getWifiConfigMaxDisableDurationMs());
1433     }
1434 
1435     /**
1436      * Helper method to mark a network permanently disabled for network selection.
1437      */
setNetworkSelectionPermanentlyDisabled( WifiConfiguration config, int disableReason)1438     private void setNetworkSelectionPermanentlyDisabled(
1439             WifiConfiguration config, int disableReason) {
1440         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1441         status.setNetworkSelectionStatus(
1442                 NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
1443         status.setDisableTime(
1444                 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1445         status.setNetworkSelectionDisableReason(disableReason);
1446         handleWifiConfigurationDisabled(config.SSID);
1447         config.status = WifiConfiguration.Status.DISABLED;
1448         mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
1449     }
1450 
1451     /**
1452      * Network Selection disable reason thresholds. These numbers are used to debounce network
1453      * failures before we disable them.
1454      *
1455      * @param reason int reason code
1456      * @return the disable threshold, or -1 if not found.
1457      */
1458     @VisibleForTesting
getNetworkSelectionDisableThreshold(@etworkSelectionDisableReason int reason)1459     public int getNetworkSelectionDisableThreshold(@NetworkSelectionDisableReason int reason) {
1460         DisableReasonInfo info = mDisableReasonInfo.get(reason);
1461         if (info == null) {
1462             Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason);
1463             return -1;
1464         } else {
1465             return info.mDisableThreshold;
1466         }
1467     }
1468 
1469     /**
1470      * Network Selection disable timeout for each kind of error. After the timeout in milliseconds,
1471      * enable the network again.
1472      */
1473     @VisibleForTesting
getNetworkSelectionDisableTimeoutMillis(@etworkSelectionDisableReason int reason)1474     public int getNetworkSelectionDisableTimeoutMillis(@NetworkSelectionDisableReason int reason) {
1475         DisableReasonInfo info = mDisableReasonInfo.get(reason);
1476         if (info == null) {
1477             Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason);
1478             return -1;
1479         } else {
1480             return info.mDisableTimeoutMillis;
1481         }
1482     }
1483 
1484     /**
1485      * Sets the allowlist ssids for the given ssid
1486      */
setAllowlistSsids(@onNull String ssid, @NonNull List<String> ssidAllowlist)1487     public void setAllowlistSsids(@NonNull String ssid, @NonNull List<String> ssidAllowlist) {
1488         mSsidAllowlistMap.put(ssid, ssidAllowlist);
1489     }
1490 }
1491