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