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