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