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