• 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.WifiManager;
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.wifi.resources.R;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.ArrayList;
44 import java.util.Calendar;
45 import java.util.Collections;
46 import java.util.HashSet;
47 import java.util.LinkedList;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.concurrent.TimeUnit;
52 import java.util.stream.Collectors;
53 import java.util.stream.Stream;
54 
55 /**
56  * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used
57  * for firmware roaming and network selection.
58  */
59 public class WifiBlocklistMonitor {
60     // A special type association rejection
61     public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0;
62     // No internet
63     public static final int REASON_NETWORK_VALIDATION_FAILURE = 1;
64     // Wrong password error
65     public static final int REASON_WRONG_PASSWORD = 2;
66     // Incorrect EAP credentials
67     public static final int REASON_EAP_FAILURE = 3;
68     // Other association rejection failures
69     public static final int REASON_ASSOCIATION_REJECTION = 4;
70     // Association timeout failures.
71     public static final int REASON_ASSOCIATION_TIMEOUT = 5;
72     // Other authentication failures
73     public static final int REASON_AUTHENTICATION_FAILURE = 6;
74     // DHCP failures
75     public static final int REASON_DHCP_FAILURE = 7;
76     // Abnormal disconnect error
77     public static final int REASON_ABNORMAL_DISCONNECT = 8;
78     // AP initiated disconnect for a given duration.
79     public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9;
80     // Avoid connecting to the failed AP when trying to reconnect on other available candidates.
81     public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10;
82     // The connected scorer has disconnected this network.
83     public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11;
84     // Non-local disconnection in the middle of connecting state
85     public static final int REASON_NONLOCAL_DISCONNECT_CONNECTING = 12;
86     // Constant being used to keep track of how many failure reasons there are.
87     public static final int NUMBER_REASON_CODES = 13;
88     public static final int INVALID_REASON = -1;
89 
90     @IntDef(prefix = { "REASON_" }, value = {
91             REASON_AP_UNABLE_TO_HANDLE_NEW_STA,
92             REASON_NETWORK_VALIDATION_FAILURE,
93             REASON_WRONG_PASSWORD,
94             REASON_EAP_FAILURE,
95             REASON_ASSOCIATION_REJECTION,
96             REASON_ASSOCIATION_TIMEOUT,
97             REASON_AUTHENTICATION_FAILURE,
98             REASON_DHCP_FAILURE,
99             REASON_ABNORMAL_DISCONNECT,
100             REASON_FRAMEWORK_DISCONNECT_MBO_OCE,
101             REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT,
102             REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
103             REASON_NONLOCAL_DISCONNECT_CONNECTING
104     })
105     @Retention(RetentionPolicy.SOURCE)
106     public @interface FailureReason {}
107 
108     // To be filled with values from the overlay.
109     private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES];
110     private boolean mFailureCountDisableThresholdArrayInitialized = false;
111     private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3);
112     private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5;
113     @VisibleForTesting
114     public static final int NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF = 5;
115     @VisibleForTesting
116     public static final long WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS = TimeUnit.HOURS.toMillis(18);
117     private static final String TAG = "WifiBlocklistMonitor";
118 
119     private final Context mContext;
120     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
121     private final WifiConnectivityHelper mConnectivityHelper;
122     private final Clock mClock;
123     private final LocalLog mLocalLog;
124     private final WifiScoreCard mWifiScoreCard;
125     private final ScoringParams mScoringParams;
126     private final WifiMetrics mWifiMetrics;
127     private final Map<Integer, BssidDisableReason> mBssidDisableReasons =
128             buildBssidDisableReasons();
129     private final SparseArray<DisableReasonInfo> mDisableReasonInfo;
130 
131     // Map of bssid to BssidStatus
132     private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>();
133     private Set<String> mDisabledSsids = new ArraySet<>();
134 
135     // Internal logger to make sure imporatant logs do not get lost.
136     private BssidBlocklistMonitorLogger mBssidBlocklistMonitorLogger =
137             new BssidBlocklistMonitorLogger(60);
138 
139     // Map of ssid to Allowlist SSIDs
140     private Map<String, List<String>> mSsidAllowlistMap = new ArrayMap<>();
141 
142     /**
143      * Verbose logging flag. Toggled by developer options.
144      */
145     private boolean mVerboseLoggingEnabled = false;
146 
147 
buildBssidDisableReasons()148     private Map<Integer, BssidDisableReason> buildBssidDisableReasons() {
149         Map<Integer, BssidDisableReason> result = new ArrayMap<>();
150         result.put(REASON_AP_UNABLE_TO_HANDLE_NEW_STA, new BssidDisableReason(
151                 "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", false, false));
152         result.put(REASON_NETWORK_VALIDATION_FAILURE, new BssidDisableReason(
153                 "REASON_NETWORK_VALIDATION_FAILURE", true, false));
154         result.put(REASON_WRONG_PASSWORD, new BssidDisableReason(
155                 "REASON_WRONG_PASSWORD", false, true));
156         result.put(REASON_EAP_FAILURE, new BssidDisableReason(
157                 "REASON_EAP_FAILURE", true, true));
158         result.put(REASON_ASSOCIATION_REJECTION, new BssidDisableReason(
159                 "REASON_ASSOCIATION_REJECTION", true, true));
160         result.put(REASON_ASSOCIATION_TIMEOUT, new BssidDisableReason(
161                 "REASON_ASSOCIATION_TIMEOUT", true, true));
162         result.put(REASON_AUTHENTICATION_FAILURE, new BssidDisableReason(
163                 "REASON_AUTHENTICATION_FAILURE", true, true));
164         result.put(REASON_DHCP_FAILURE, new BssidDisableReason(
165                 "REASON_DHCP_FAILURE", true, false));
166         result.put(REASON_ABNORMAL_DISCONNECT, new BssidDisableReason(
167                 "REASON_ABNORMAL_DISCONNECT", true, false));
168         result.put(REASON_FRAMEWORK_DISCONNECT_MBO_OCE, new BssidDisableReason(
169                 "REASON_FRAMEWORK_DISCONNECT_MBO_OCE", false, false));
170         result.put(REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, new BssidDisableReason(
171                 "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT", false, false));
172         result.put(REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, new BssidDisableReason(
173                 "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE", true, false));
174         // TODO: b/174166637, add the same reason code in SSID blocklist and mark ignoreIfOnlyBssid
175         // to true once it is covered in SSID blocklist.
176         result.put(REASON_NONLOCAL_DISCONNECT_CONNECTING, new BssidDisableReason(
177                 "REASON_NONLOCAL_DISCONNECT_CONNECTING", true, false));
178         return result;
179     }
180 
181     class BssidDisableReason {
182         public final String reasonString;
183         public final boolean isLowRssiSensitive;
184         public final boolean ignoreIfOnlyBssid;
185 
BssidDisableReason(String reasonString, boolean isLowRssiSensitive, boolean ignoreIfOnlyBssid)186         BssidDisableReason(String reasonString, boolean isLowRssiSensitive,
187                 boolean ignoreIfOnlyBssid) {
188             this.reasonString = reasonString;
189             this.isLowRssiSensitive = isLowRssiSensitive;
190             this.ignoreIfOnlyBssid = ignoreIfOnlyBssid;
191         }
192     }
193 
194     /**
195      * Create a new instance of WifiBlocklistMonitor
196      */
WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics)197     WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper,
198             WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog,
199             WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics) {
200         mContext = context;
201         mConnectivityHelper = connectivityHelper;
202         mWifiLastResortWatchdog = wifiLastResortWatchdog;
203         mClock = clock;
204         mLocalLog = localLog;
205         mWifiScoreCard = wifiScoreCard;
206         mScoringParams = scoringParams;
207         mDisableReasonInfo = DISABLE_REASON_INFOS.clone();
208         mWifiMetrics = wifiMetrics;
209         loadCustomConfigsForDisableReasonInfos();
210     }
211 
212     // A helper to log debugging information in the local log buffer, which can
213     // be retrieved in bugreport.
localLog(String log)214     private void localLog(String log) {
215         mLocalLog.log(log);
216     }
217 
218     /**
219      * calculates the blocklist duration based on the current failure streak with exponential
220      * backoff.
221      * @param failureStreak should be greater or equal to 0.
222      * @return duration to block the BSSID in milliseconds
223      */
getBlocklistDurationWithExponentialBackoff(int failureStreak, int baseBlocklistDurationMs)224     private long getBlocklistDurationWithExponentialBackoff(int failureStreak,
225             int baseBlocklistDurationMs) {
226         failureStreak = Math.min(failureStreak, mContext.getResources().getInteger(
227                 R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap));
228         if (failureStreak < 1) {
229             return baseBlocklistDurationMs;
230         }
231         return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs);
232     }
233 
234     /**
235      * Dump the local log buffer and other internal state of WifiBlocklistMonitor.
236      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)237     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
238         pw.println("Dump of WifiBlocklistMonitor");
239         mLocalLog.dump(fd, pw, args);
240         pw.println("WifiBlocklistMonitor - Bssid blocklist begin ----");
241         mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry));
242         pw.println("WifiBlocklistMonitor - Bssid blocklist end ----");
243         mBssidBlocklistMonitorLogger.dump(pw);
244     }
245 
addToBlocklist(@onNull BssidStatus entry, long durationMs, @FailureReason int reason, int rssi)246     private void addToBlocklist(@NonNull BssidStatus entry, long durationMs,
247             @FailureReason int reason, int rssi) {
248         entry.setAsBlocked(durationMs, reason, rssi);
249         localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid
250                 + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason)
251                 + ", rssi=" + rssi);
252     }
253 
254     /**
255      * increments the number of failures for the given bssid and returns the number of failures so
256      * far.
257      * @return the BssidStatus for the BSSID
258      */
incrementFailureCountForBssid( @onNull String bssid, @NonNull String ssid, int reasonCode)259     private @NonNull BssidStatus incrementFailureCountForBssid(
260             @NonNull String bssid, @NonNull String ssid, int reasonCode) {
261         BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
262         status.incrementFailureCount(reasonCode);
263         return status;
264     }
265 
266     /**
267      * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist.
268      */
getOrCreateBssidStatus(@onNull String bssid, @NonNull String ssid)269     private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid,
270             @NonNull String ssid) {
271         BssidStatus status = mBssidStatusMap.get(bssid);
272         if (status == null || !ssid.equals(status.ssid)) {
273             if (status != null) {
274                 localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from "
275                         + status.ssid + " to " + ssid);
276             }
277             status = new BssidStatus(bssid, ssid);
278             mBssidStatusMap.put(bssid, status);
279         }
280         return status;
281     }
282 
isValidNetworkAndFailureReason(String bssid, String ssid, @FailureReason int reasonCode)283     private boolean isValidNetworkAndFailureReason(String bssid, String ssid,
284             @FailureReason int reasonCode) {
285         if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)
286                 || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)
287                 || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) {
288             Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
289                     + ", reasonCode=" + reasonCode);
290             return false;
291         }
292         return true;
293     }
294 
shouldWaitForWatchdogToTriggerFirst(String bssid, @FailureReason int reasonCode)295     private boolean shouldWaitForWatchdogToTriggerFirst(String bssid,
296             @FailureReason int reasonCode) {
297         boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION
298                 || reasonCode == REASON_AUTHENTICATION_FAILURE
299                 || reasonCode == REASON_DHCP_FAILURE;
300         return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid);
301     }
302 
303     /**
304      * Block any attempts to auto-connect to the BSSID for the specified duration.
305      * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration,
306      * and thus will not increase the failure streak counters.
307      * @param bssid identifies the AP to block.
308      * @param ssid identifies the SSID the AP belongs to.
309      * @param durationMs duration in millis to block.
310      * @param blockReason reason for blocking the BSSID.
311      * @param rssi the latest RSSI observed.
312      */
blockBssidForDurationMs(@onNull String bssid, @NonNull String ssid, long durationMs, @FailureReason int blockReason, int rssi)313     public void blockBssidForDurationMs(@NonNull String bssid, @NonNull String ssid,
314             long durationMs, @FailureReason int blockReason, int rssi) {
315         if (durationMs <= 0 || !isValidNetworkAndFailureReason(bssid, ssid, blockReason)) {
316             Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid
317                     + ", durationMs=" + durationMs + ", blockReason=" + blockReason
318                     + ", rssi=" + rssi);
319             return;
320         }
321         BssidStatus status = getOrCreateBssidStatus(bssid, ssid);
322         if (status.isInBlocklist
323                 && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) {
324             // Return because this BSSID is already being blocked for a longer time.
325             return;
326         }
327         addToBlocklist(status, durationMs, blockReason, rssi);
328     }
329 
getFailureReasonString(@ailureReason int reasonCode)330     private String getFailureReasonString(@FailureReason int reasonCode) {
331         if (reasonCode == INVALID_REASON) {
332             return "INVALID_REASON";
333         }
334         BssidDisableReason disableReason = mBssidDisableReasons.get(reasonCode);
335         if (disableReason == null) {
336             return "REASON_UNKNOWN";
337         }
338         return disableReason.reasonString;
339     }
340 
getFailureThresholdForReason(@ailureReason int reasonCode)341     private int getFailureThresholdForReason(@FailureReason int reasonCode) {
342         if (mFailureCountDisableThresholdArrayInitialized) {
343             return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
344         }
345         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] =
346                 mContext.getResources().getInteger(
347                         R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold);
348         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] =
349                 mContext.getResources().getInteger(R.integer
350                         .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold);
351         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] =
352                 mContext.getResources().getInteger(
353                         R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold);
354         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] =
355                 mContext.getResources().getInteger(
356                         R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold);
357         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] =
358                 mContext.getResources().getInteger(
359                         R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold);
360         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] =
361                 mContext.getResources().getInteger(
362                         R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold);
363         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] =
364                 mContext.getResources().getInteger(
365                         R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold);
366         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] =
367                 mContext.getResources().getInteger(
368                         R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold);
369         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] =
370                 mContext.getResources().getInteger(
371                         R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold);
372         FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NONLOCAL_DISCONNECT_CONNECTING] =
373                 mContext.getResources().getInteger(R.integer
374                         .config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold);
375         mFailureCountDisableThresholdArrayInitialized = true;
376         return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode];
377     }
378 
handleBssidConnectionFailureInternal(String bssid, String ssid, @FailureReason int reasonCode, int rssi)379     private boolean handleBssidConnectionFailureInternal(String bssid, String ssid,
380             @FailureReason int reasonCode, int rssi) {
381         BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode);
382         int failureThreshold = getFailureThresholdForReason(reasonCode);
383         int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode);
384         if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) {
385             // To rule out potential device side issues, don't add to blocklist if
386             // WifiLastResortWatchdog is still not triggered
387             if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) {
388                 localLog("Ignoring failure to wait for watchdog to trigger first.");
389                 return false;
390             }
391             int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode);
392             addToBlocklist(entry,
393                     getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs),
394                     reasonCode, rssi);
395             mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode);
396             return true;
397         }
398         return false;
399     }
400 
getBaseBlockDurationForReason(int blockReason)401     private int getBaseBlockDurationForReason(int blockReason) {
402         switch (blockReason) {
403             case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
404                 return mContext.getResources().getInteger(R.integer
405                         .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs);
406             default:
407                 return mContext.getResources().getInteger(
408                         R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs);
409         }
410     }
411 
412     /**
413      * Note a failure event on a bssid and perform appropriate actions.
414      * @return True if the blocklist has been modified.
415      */
handleBssidConnectionFailure(String bssid, String ssid, @FailureReason int reasonCode, int rssi)416     public boolean handleBssidConnectionFailure(String bssid, String ssid,
417             @FailureReason int reasonCode, int rssi) {
418         if (!isValidNetworkAndFailureReason(bssid, ssid, reasonCode)) {
419             return false;
420         }
421         BssidDisableReason bssidDisableReason = mBssidDisableReasons.get(reasonCode);
422         if (bssidDisableReason == null) {
423             Log.e(TAG, "Bssid disable reason not found. ReasonCode=" + reasonCode);
424             return false;
425         }
426         if (bssidDisableReason.ignoreIfOnlyBssid && !mDisabledSsids.contains(ssid)
427                 && mWifiLastResortWatchdog.isBssidOnlyApOfSsid(bssid)) {
428             localLog("Ignoring BSSID failure due to no other APs available. BSSID=" + bssid);
429             return false;
430         }
431         if (reasonCode == REASON_ABNORMAL_DISCONNECT) {
432             long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid);
433             // only count disconnects that happen shortly after a connection.
434             if (mClock.getWallClockMillis() - connectionTime
435                     > mContext.getResources().getInteger(
436                             R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) {
437                 return false;
438             }
439         }
440         return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi);
441     }
442 
443     /**
444      * To be called when a WifiConfiguration is either temporarily disabled or permanently disabled.
445      * @param ssid of the WifiConfiguration that is disabled.
446      */
handleWifiConfigurationDisabled(String ssid)447     public void handleWifiConfigurationDisabled(String ssid) {
448         if (ssid != null) {
449             mDisabledSsids.add(ssid);
450         }
451     }
452 
453     /**
454      * Note a connection success event on a bssid and clear appropriate failure counters.
455      */
handleBssidConnectionSuccess(@onNull String bssid, @NonNull String ssid)456     public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) {
457         mDisabledSsids.remove(ssid);
458         /**
459          * First reset the blocklist streak.
460          * This needs to be done even if a BssidStatus is not found, since the BssidStatus may
461          * have been removed due to blocklist timeout.
462          */
463         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA);
464         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD);
465         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE);
466         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION);
467         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT);
468         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE);
469         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
470                 REASON_NONLOCAL_DISCONNECT_CONNECTING);
471 
472         long connectionTime = mClock.getWallClockMillis();
473         long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs(
474                 ssid, bssid, connectionTime);
475         if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
476             mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT);
477             mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid,
478                     REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE);
479         }
480 
481         BssidStatus status = mBssidStatusMap.get(bssid);
482         if (status == null) {
483             return;
484         }
485         // Clear the L2 failure counters
486         status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0;
487         status.failureCount[REASON_WRONG_PASSWORD] = 0;
488         status.failureCount[REASON_EAP_FAILURE] = 0;
489         status.failureCount[REASON_ASSOCIATION_REJECTION] = 0;
490         status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0;
491         status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0;
492         status.failureCount[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 0;
493         if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) {
494             status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0;
495         }
496     }
497 
498     /**
499      * Note a successful network validation on a BSSID and clear appropriate failure counters.
500      * And then remove the BSSID from blocklist.
501      */
handleNetworkValidationSuccess(@onNull String bssid, @NonNull String ssid)502     public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) {
503         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE);
504         BssidStatus status = mBssidStatusMap.get(bssid);
505         if (status == null) {
506             return;
507         }
508         status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0;
509         /**
510          * Network validation may take more than 1 tries to succeed.
511          * remove the BSSID from blocklist to make sure we are not accidentally blocking good
512          * BSSIDs.
513          **/
514         if (status.isInBlocklist) {
515             mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "Network validation success");
516             mBssidStatusMap.remove(bssid);
517         }
518     }
519 
520     /**
521      * Note a successful DHCP provisioning and clear appropriate faliure counters.
522      */
handleDhcpProvisioningSuccess(@onNull String bssid, @NonNull String ssid)523     public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) {
524         mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE);
525         BssidStatus status = mBssidStatusMap.get(bssid);
526         if (status == null) {
527             return;
528         }
529         status.failureCount[REASON_DHCP_FAILURE] = 0;
530     }
531 
532     /**
533      * Note the removal of a network from the Wifi stack's internal database and reset
534      * appropriate failure counters.
535      * @param ssid
536      */
handleNetworkRemoved(@onNull String ssid)537     public void handleNetworkRemoved(@NonNull String ssid) {
538         clearBssidBlocklistForSsid(ssid);
539         mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid);
540     }
541 
542     /**
543      * Clears the blocklist for BSSIDs associated with the input SSID only.
544      * @param ssid
545      */
clearBssidBlocklistForSsid(@onNull String ssid)546     public void clearBssidBlocklistForSsid(@NonNull String ssid) {
547         int prevSize = mBssidStatusMap.size();
548         mBssidStatusMap.entrySet().removeIf(e -> {
549             BssidStatus status = e.getValue();
550             if (status.ssid == null) {
551                 return false;
552             }
553             if (status.ssid.equals(ssid)) {
554                 mBssidBlocklistMonitorLogger.logBssidUnblocked(
555                         status, "clearBssidBlocklistForSsid");
556                 return true;
557             }
558             return false;
559         });
560         int diff = prevSize - mBssidStatusMap.size();
561         if (diff > 0) {
562             localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid
563                     + ", num BSSIDs cleared=" + diff);
564         }
565     }
566 
567     /**
568      * Clears the BSSID blocklist and failure counters.
569      */
clearBssidBlocklist()570     public void clearBssidBlocklist() {
571         if (mBssidStatusMap.size() > 0) {
572             int prevSize = mBssidStatusMap.size();
573             for (BssidStatus status : mBssidStatusMap.values()) {
574                 mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "clearBssidBlocklist");
575             }
576             mBssidStatusMap.clear();
577             localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared="
578                     + (prevSize - mBssidStatusMap.size()));
579         }
580         mDisabledSsids.clear();
581     }
582 
583     /**
584      * @param ssid
585      * @return the number of BSSIDs currently in the blocklist for the |ssid|.
586      */
updateAndGetNumBlockedBssidsForSsid(@onNull String ssid)587     public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) {
588         return (int) updateAndGetBssidBlocklistInternal()
589                 .filter(entry -> ssid.equals(entry.ssid)).count();
590     }
591 
getNumBlockedBssidsForSsids(@onNull Set<String> ssids)592     private int getNumBlockedBssidsForSsids(@NonNull Set<String> ssids) {
593         if (ssids.isEmpty()) {
594             return 0;
595         }
596         return (int) mBssidStatusMap.values().stream()
597                 .filter(entry -> entry.isInBlocklist && ssids.contains(entry.ssid))
598                 .count();
599     }
600 
601     /**
602      * Overloaded version of updateAndGetBssidBlocklist.
603      * Accepts a @Nullable String ssid as input, and updates the firmware roaming
604      * configuration if the blocklist for the input ssid has been changed.
605      * @param ssids set of ssids to update firmware roaming configuration for.
606      * @return Set of BSSIDs currently in the blocklist
607      */
updateAndGetBssidBlocklistForSsids(@onNull Set<String> ssids)608     public Set<String> updateAndGetBssidBlocklistForSsids(@NonNull Set<String> ssids) {
609         int numBefore = getNumBlockedBssidsForSsids(ssids);
610         Set<String> bssidBlocklist = updateAndGetBssidBlocklist();
611         if (getNumBlockedBssidsForSsids(ssids) != numBefore) {
612             updateFirmwareRoamingConfiguration(ssids);
613         }
614         return bssidBlocklist;
615     }
616 
617     /**
618      * Gets the BSSIDs that are currently in the blocklist.
619      * @return Set of BSSIDs currently in the blocklist
620      */
updateAndGetBssidBlocklist()621     public Set<String> updateAndGetBssidBlocklist() {
622         return updateAndGetBssidBlocklistInternal()
623                 .map(entry -> entry.bssid)
624                 .collect(Collectors.toSet());
625     }
626 
627     /**
628      * Gets the list of block reasons for BSSIDs currently in the blocklist.
629      * @return The set of unique reasons for blocking BSSIDs with this SSID.
630      */
getFailureReasonsForSsid(@onNull String ssid)631     public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) {
632         if (ssid == null) {
633             return Collections.emptySet();
634         }
635         return mBssidStatusMap.values().stream()
636                 .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
637                 .map(entry -> entry.blockReason)
638                 .collect(Collectors.toSet());
639     }
640 
641     /**
642      * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI.
643      * @param scanDetails
644      */
tryEnablingBlockedBssids(List<ScanDetail> scanDetails)645     public void tryEnablingBlockedBssids(List<ScanDetail> scanDetails) {
646         if (scanDetails == null) {
647             return;
648         }
649         for (ScanDetail scanDetail : scanDetails) {
650             ScanResult scanResult = scanDetail.getScanResult();
651             if (scanResult == null) {
652                 continue;
653             }
654             BssidStatus status = mBssidStatusMap.get(scanResult.BSSID);
655             if (status == null || !status.isInBlocklist
656                     || !isLowRssiSensitiveFailure(status.blockReason)) {
657                 continue;
658             }
659             int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency);
660             if (status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi
661                     && scanResult.level - status.lastRssi >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID) {
662                 mBssidBlocklistMonitorLogger.logBssidUnblocked(
663                         status, "rssi significantly improved");
664                 mBssidStatusMap.remove(status.bssid);
665             }
666         }
667     }
668 
isLowRssiSensitiveFailure(int blockReason)669     private boolean isLowRssiSensitiveFailure(int blockReason) {
670         return mBssidDisableReasons.get(blockReason) == null ? false
671                 : mBssidDisableReasons.get(blockReason).isLowRssiSensitive;
672     }
673 
674     /**
675      * Removes expired BssidStatus entries and then return remaining entries in the blocklist.
676      * @return Stream of BssidStatus for BSSIDs that are in the blocklist.
677      */
updateAndGetBssidBlocklistInternal()678     private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() {
679         Stream.Builder<BssidStatus> builder = Stream.builder();
680         long curTime = mClock.getWallClockMillis();
681         mBssidStatusMap.entrySet().removeIf(e -> {
682             BssidStatus status = e.getValue();
683             if (status.isInBlocklist) {
684                 if (status.blocklistEndTimeMs < curTime) {
685                     mBssidBlocklistMonitorLogger.logBssidUnblocked(
686                             status, "updateAndGetBssidBlocklistInternal");
687                     return true;
688                 }
689                 builder.accept(status);
690             }
691             return false;
692         });
693         return builder.build();
694     }
695 
696     /**
697      * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming
698      * to those BSSIDs.
699      * @param ssids
700      */
updateFirmwareRoamingConfiguration(@onNull Set<String> ssids)701     public void updateFirmwareRoamingConfiguration(@NonNull Set<String> ssids) {
702         if (!mConnectivityHelper.isFirmwareRoamingSupported()) {
703             return;
704         }
705         ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal()
706                 .filter(entry -> ssids.contains(entry.ssid))
707                 .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs))
708                 .map(entry -> entry.bssid)
709                 .collect(Collectors.toCollection(ArrayList::new));
710         int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlocklistBssid();
711         if (fwMaxBlocklistSize <= 0) {
712             Log.e(TAG, "Invalid max BSSID blocklist size:  " + fwMaxBlocklistSize);
713             return;
714         }
715         // Having the blocklist size exceeding firmware max limit is unlikely because we have
716         // already flitered based on SSID. But just in case this happens, we are prioritizing
717         // sending down BSSIDs blocked for the longest time.
718         if (bssidBlocklist.size() > fwMaxBlocklistSize) {
719             bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0,
720                     fwMaxBlocklistSize));
721         }
722 
723         // Collect all the allowed SSIDs
724         Set<String> allowedSsidSet = new HashSet<>();
725         for (String ssid : ssids) {
726             List<String> allowedSsidsForSsid = mSsidAllowlistMap.get(ssid);
727             if (allowedSsidsForSsid != null) {
728                 allowedSsidSet.addAll(allowedSsidsForSsid);
729             }
730         }
731         ArrayList<String> ssidAllowlist = new ArrayList<>(allowedSsidSet);
732         int allowlistSize = ssidAllowlist.size();
733         int maxAllowlistSize = mConnectivityHelper.getMaxNumAllowlistSsid();
734         if (maxAllowlistSize <= 0) {
735             Log.wtf(TAG, "Invalid max SSID allowlist size:  " + maxAllowlistSize);
736             return;
737         }
738         if (allowlistSize > maxAllowlistSize) {
739             ssidAllowlist = new ArrayList<>(ssidAllowlist.subList(0, maxAllowlistSize));
740             localLog("Trim down SSID allowlist size from " + allowlistSize + " to "
741                     + ssidAllowlist.size());
742         }
743 
744         // plumb down to HAL
745         String message = "set firmware roaming configurations. "
746                 + "bssidBlocklist=";
747         if (bssidBlocklist.size() == 0) {
748             message += "<EMPTY>";
749         } else {
750             message += String.join(", ", bssidBlocklist);
751         }
752         if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, ssidAllowlist)) {
753             Log.e(TAG, "Failed to " + message);
754             mBssidBlocklistMonitorLogger.log("Failed to " + message);
755         } else {
756             mBssidBlocklistMonitorLogger.log("Successfully " + message);
757         }
758     }
759 
760     @VisibleForTesting
getBssidBlocklistMonitorLoggerSize()761     public int getBssidBlocklistMonitorLoggerSize() {
762         return mBssidBlocklistMonitorLogger.size();
763     }
764 
765     private class BssidBlocklistMonitorLogger {
766         private LinkedList<String> mLogBuffer = new LinkedList<>();
767         private int mBufferSize;
768 
BssidBlocklistMonitorLogger(int bufferSize)769         BssidBlocklistMonitorLogger(int bufferSize) {
770             mBufferSize = bufferSize;
771         }
772 
logBssidUnblocked(BssidStatus bssidStatus, String unblockReason)773         public void logBssidUnblocked(BssidStatus bssidStatus, String unblockReason) {
774             // only log history for Bssids that had been blocked.
775             if (bssidStatus == null || !bssidStatus.isInBlocklist) {
776                 return;
777             }
778             StringBuilder sb = createStringBuilderWithLogTime();
779             sb.append(", Bssid unblocked, Reason=" + unblockReason);
780             sb.append(", Unblocked BssidStatus={" + bssidStatus.toString() + "}");
781             logInternal(sb.toString());
782         }
783 
784         // cache a single line of log message in the rotating buffer
log(String message)785         public void log(String message) {
786             if (message == null) {
787                 return;
788             }
789             StringBuilder sb = createStringBuilderWithLogTime();
790             sb.append(" " + message);
791             logInternal(sb.toString());
792         }
793 
createStringBuilderWithLogTime()794         private StringBuilder createStringBuilderWithLogTime() {
795             StringBuilder sb = new StringBuilder();
796             Calendar calendar = Calendar.getInstance();
797             calendar.setTimeInMillis(mClock.getWallClockMillis());
798             sb.append("logTimeMs=" + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
799                     calendar, calendar, calendar, calendar));
800             return sb;
801         }
802 
logInternal(String message)803         private void logInternal(String message) {
804             mLogBuffer.add(message);
805             if (mLogBuffer.size() > mBufferSize) {
806                 mLogBuffer.removeFirst();
807             }
808         }
809 
810         @VisibleForTesting
size()811         public int size() {
812             return mLogBuffer.size();
813         }
814 
dump(PrintWriter pw)815         public void dump(PrintWriter pw) {
816             pw.println("WifiBlocklistMonitor - Bssid blocklist logs begin ----");
817             for (String line : mLogBuffer) {
818                 pw.println(line);
819             }
820             pw.println("WifiBlocklistMonitor - Bssid blocklist logs end ----");
821         }
822     }
823 
824     /**
825      * Helper class that counts the number of failures per BSSID.
826      */
827     private class BssidStatus {
828         public final String bssid;
829         public final String ssid;
830         public final int[] failureCount = new int[NUMBER_REASON_CODES];
831         public int blockReason = INVALID_REASON; // reason of blocking this BSSID
832         // The latest RSSI that's seen before this BSSID is added to blocklist.
833         public int lastRssi = 0;
834 
835         // The following are used to flag how long this BSSID stays in the blocklist.
836         public boolean isInBlocklist;
837         public long blocklistEndTimeMs;
838         public long blocklistStartTimeMs;
839 
BssidStatus(String bssid, String ssid)840         BssidStatus(String bssid, String ssid) {
841             this.bssid = bssid;
842             this.ssid = ssid;
843         }
844 
845         /**
846          * increments the failure count for the reasonCode by 1.
847          * @return the incremented failure count
848          */
incrementFailureCount(int reasonCode)849         public int incrementFailureCount(int reasonCode) {
850             return ++failureCount[reasonCode];
851         }
852 
853         /**
854          * Set this BSSID as blocked for the specified duration.
855          * @param durationMs
856          * @param blockReason
857          * @param rssi
858          */
setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi)859         public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) {
860             isInBlocklist = true;
861             blocklistStartTimeMs = mClock.getWallClockMillis();
862             blocklistEndTimeMs = blocklistStartTimeMs + durationMs;
863             this.blockReason = blockReason;
864             lastRssi = rssi;
865             mWifiMetrics.incrementBssidBlocklistCount(blockReason);
866         }
867 
868         @Override
toString()869         public String toString() {
870             StringBuilder sb = new StringBuilder();
871             sb.append("BSSID=" + bssid);
872             sb.append(", SSID=" + ssid);
873             sb.append(", isInBlocklist=" + isInBlocklist);
874             if (isInBlocklist) {
875                 sb.append(", blockReason=" + getFailureReasonString(blockReason));
876                 sb.append(", lastRssi=" + lastRssi);
877                 Calendar calendar = Calendar.getInstance();
878                 calendar.setTimeInMillis(blocklistStartTimeMs);
879                 sb.append(", blocklistStartTimeMs="
880                         + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
881                         calendar, calendar, calendar, calendar));
882                 calendar.setTimeInMillis(blocklistEndTimeMs);
883                 sb.append(", blocklistEndTimeMs="
884                         + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar,
885                         calendar, calendar, calendar, calendar));
886             }
887             return sb.toString();
888         }
889     }
890 
891     /**
892      * Enable/disable verbose logging in WifiBlocklistMonitor.
893      */
enableVerboseLogging(boolean verbose)894     public void enableVerboseLogging(boolean verbose) {
895         mVerboseLoggingEnabled = verbose;
896     }
897 
898     /**
899      * Modify the internal copy of DisableReasonInfo with custom configurations defined in
900      * an overlay.
901      */
loadCustomConfigsForDisableReasonInfos()902     private void loadCustomConfigsForDisableReasonInfos() {
903         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION,
904                 new DisableReasonInfo(
905                         // Note that there is a space at the end of this string. Cannot fix
906                         // since this string is persisted.
907                         "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
908                         mContext.getResources().getInteger(R.integer
909                                 .config_wifiDisableReasonAssociationRejectionThreshold),
910                         5 * 60 * 1000));
911 
912         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE,
913                 new DisableReasonInfo(
914                         "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
915                         mContext.getResources().getInteger(R.integer
916                                 .config_wifiDisableReasonAuthenticationFailureThreshold),
917                         5 * 60 * 1000));
918 
919         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_DHCP_FAILURE,
920                 new DisableReasonInfo(
921                         "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
922                         mContext.getResources().getInteger(R.integer
923                                 .config_wifiDisableReasonDhcpFailureThreshold),
924                         5 * 60 * 1000));
925 
926         mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND,
927                 new DisableReasonInfo(
928                         "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND",
929                         mContext.getResources().getInteger(R.integer
930                                 .config_wifiDisableReasonNetworkNotFoundThreshold),
931                         5 * 60 * 1000));
932     }
933 
934     /** Update DisableReasonInfo with carrier configurations defined in an overlay. **/
loadCarrierConfigsForDisableReasonInfos()935     public void loadCarrierConfigsForDisableReasonInfos() {
936         int duration = mContext.getResources().getInteger(
937                 R.integer.config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs);
938         DisableReasonInfo disableReasonInfo = new DisableReasonInfo(
939                 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR",
940                 mContext.getResources().getInteger(R.integer
941                         .config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold),
942                 duration);
943         mDisableReasonInfo.put(
944                 NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR,
945                 disableReasonInfo);
946     }
947 
948     /**
949      * Returns true if the disable duration for this WifiConfiguration has passed. Returns false
950      * if the WifiConfiguration is either not disabled or is permanently disabled.
951      */
shouldEnableNetwork(WifiConfiguration config)952     public boolean shouldEnableNetwork(WifiConfiguration config) {
953         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
954         if (networkStatus.isNetworkTemporaryDisabled()) {
955             long timeDifferenceMs =
956                     mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime();
957             int disableReason = networkStatus.getNetworkSelectionDisableReason();
958             long disableTimeoutMs = (long) getNetworkSelectionDisableTimeoutMillis(disableReason);
959             int exponentialBackoffCount = mWifiScoreCard.lookupNetwork(config.SSID)
960                     .getRecentStats().getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE)
961                     - NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF;
962             for (int i = 0; i < exponentialBackoffCount; i++) {
963                 disableTimeoutMs *= 2;
964                 if (disableTimeoutMs > WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS) {
965                     disableTimeoutMs = WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS;
966                     break;
967                 }
968             }
969             if (timeDifferenceMs >= disableTimeoutMs) {
970                 return true;
971             }
972         }
973         return false;
974     }
975 
976     /**
977      * Update a network's status (both internal and public) according to the update reason and
978      * its current state. This method is expects to directly modify the internal WifiConfiguration
979      * that is stored by WifiConfigManager.
980      *
981      * @param config the internal WifiConfiguration to be updated.
982      * @param reason reason code for update.
983      * @return true if the input configuration has been updated, false otherwise.
984      */
updateNetworkSelectionStatus(WifiConfiguration config, int reason)985     public boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
986         if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) {
987             Log.e(TAG, "Invalid Network disable reason " + reason);
988             return false;
989         }
990         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
991         if (reason != NetworkSelectionStatus.DISABLED_NONE) {
992             // Do not update SSID blocklist with information if this is the only
993             // SSID be observed. By ignoring it we will cause additional failures
994             // which will trigger Watchdog.
995             if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION
996                     || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE
997                     || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) {
998                 if (mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()) {
999                     if (mVerboseLoggingEnabled) {
1000                         Log.v(TAG, "Ignore update network selection status "
1001                                 + "since Watchdog trigger is activated");
1002                     }
1003                     return false;
1004                 }
1005             }
1006 
1007             networkStatus.incrementDisableReasonCounter(reason);
1008             // For network disable reasons, we should only update the status if we cross the
1009             // threshold.
1010             int disableReasonCounter = networkStatus.getDisableReasonCounter(reason);
1011             int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason);
1012             if (disableReasonCounter < disableReasonThreshold) {
1013                 if (mVerboseLoggingEnabled) {
1014                     Log.v(TAG, "Disable counter for network " + config.getPrintableSsid()
1015                             + " for reason "
1016                             + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason)
1017                             + " is " + networkStatus.getDisableReasonCounter(reason)
1018                             + " and threshold is " + disableReasonThreshold);
1019                 }
1020                 return true;
1021             }
1022         }
1023         setNetworkSelectionStatus(config, reason);
1024         return true;
1025     }
1026 
1027     /**
1028      * Sets a network's status (both internal and public) according to the update reason and
1029      * its current state.
1030      *
1031      * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the
1032      * public {@link WifiConfiguration#status} field if the network is either enabled or
1033      * permanently disabled.
1034      *
1035      * @param config network to be updated.
1036      * @param reason reason code for update.
1037      */
setNetworkSelectionStatus(WifiConfiguration config, int reason)1038     private void setNetworkSelectionStatus(WifiConfiguration config, int reason) {
1039         NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
1040         if (reason == NetworkSelectionStatus.DISABLED_NONE) {
1041             setNetworkSelectionEnabled(config);
1042         } else if (getNetworkSelectionDisableTimeoutMillis(reason)
1043                 != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) {
1044             setNetworkSelectionTemporarilyDisabled(config, reason);
1045         } else {
1046             setNetworkSelectionPermanentlyDisabled(config, reason);
1047         }
1048         localLog("setNetworkSelectionStatus: configKey=" + config.getProfileKey()
1049                 + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
1050                 + networkStatus.getNetworkSelectionDisableReasonString());
1051     }
1052 
1053     /**
1054      * Helper method to mark a network enabled for network selection.
1055      */
setNetworkSelectionEnabled(WifiConfiguration config)1056     private void setNetworkSelectionEnabled(WifiConfiguration config) {
1057         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1058         if (status.getNetworkSelectionStatus()
1059                 != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) {
1060             localLog("setNetworkSelectionEnabled: configKey=" + config.getProfileKey()
1061                     + " old networkStatus=" + status.getNetworkStatusString()
1062                     + " disableReason=" + status.getNetworkSelectionDisableReasonString());
1063         }
1064         status.setNetworkSelectionStatus(
1065                 NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
1066         status.setDisableTime(
1067                 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1068         status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE);
1069 
1070         // Clear out all the disable reason counters.
1071         status.clearDisableReasonCounter();
1072         config.status = WifiConfiguration.Status.ENABLED;
1073     }
1074 
1075     /**
1076      * Helper method to mark a network temporarily disabled for network selection.
1077      */
setNetworkSelectionTemporarilyDisabled( WifiConfiguration config, int disableReason)1078     private void setNetworkSelectionTemporarilyDisabled(
1079             WifiConfiguration config, int disableReason) {
1080         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1081         status.setNetworkSelectionStatus(
1082                 NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
1083         // Only need a valid time filled in for temporarily disabled networks.
1084         status.setDisableTime(mClock.getElapsedSinceBootMillis());
1085         status.setNetworkSelectionDisableReason(disableReason);
1086         handleWifiConfigurationDisabled(config.SSID);
1087         mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
1088     }
1089 
1090     /**
1091      * Helper method to mark a network permanently disabled for network selection.
1092      */
setNetworkSelectionPermanentlyDisabled( WifiConfiguration config, int disableReason)1093     private void setNetworkSelectionPermanentlyDisabled(
1094             WifiConfiguration config, int disableReason) {
1095         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
1096         status.setNetworkSelectionStatus(
1097                 NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
1098         status.setDisableTime(
1099                 NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
1100         status.setNetworkSelectionDisableReason(disableReason);
1101         handleWifiConfigurationDisabled(config.SSID);
1102         config.status = WifiConfiguration.Status.DISABLED;
1103         mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason);
1104     }
1105 
1106     /**
1107      * Network Selection disable reason thresholds. These numbers are used to debounce network
1108      * failures before we disable them.
1109      *
1110      * @param reason int reason code
1111      * @return the disable threshold, or -1 if not found.
1112      */
1113     @VisibleForTesting
getNetworkSelectionDisableThreshold(@etworkSelectionDisableReason int reason)1114     public int getNetworkSelectionDisableThreshold(@NetworkSelectionDisableReason int reason) {
1115         DisableReasonInfo info = mDisableReasonInfo.get(reason);
1116         if (info == null) {
1117             Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason);
1118             return -1;
1119         } else {
1120             return info.mDisableThreshold;
1121         }
1122     }
1123 
1124     /**
1125      * Network Selection disable timeout for each kind of error. After the timeout in milliseconds,
1126      * enable the network again.
1127      */
1128     @VisibleForTesting
getNetworkSelectionDisableTimeoutMillis(@etworkSelectionDisableReason int reason)1129     public int getNetworkSelectionDisableTimeoutMillis(@NetworkSelectionDisableReason int reason) {
1130         DisableReasonInfo info = mDisableReasonInfo.get(reason);
1131         if (info == null) {
1132             Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason);
1133             return -1;
1134         } else {
1135             return info.mDisableTimeoutMillis;
1136         }
1137     }
1138 
1139     /**
1140      * Sets the allowlist ssids for the given ssid
1141      */
setAllowlistSsids(@onNull String ssid, @NonNull List<String> ssidAllowlist)1142     public void setAllowlistSsids(@NonNull String ssid, @NonNull List<String> ssidAllowlist) {
1143         mSsidAllowlistMap.put(ssid, ssidAllowlist);
1144     }
1145 }
1146