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