| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi; |
| |
| import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLE_REASON_INFOS; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; |
| import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DisableReasonInfo; |
| import android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NetworkSelectionDisableReason; |
| import android.net.wifi.WifiManager; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.LocalLog; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| /** |
| * This class manages the addition and removal of BSSIDs to the BSSID blocklist, which is used |
| * for firmware roaming and network selection. |
| */ |
| public class WifiBlocklistMonitor { |
| // A special type association rejection |
| public static final int REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 0; |
| // No internet |
| public static final int REASON_NETWORK_VALIDATION_FAILURE = 1; |
| // Wrong password error |
| public static final int REASON_WRONG_PASSWORD = 2; |
| // Incorrect EAP credentials |
| public static final int REASON_EAP_FAILURE = 3; |
| // Other association rejection failures |
| public static final int REASON_ASSOCIATION_REJECTION = 4; |
| // Association timeout failures. |
| public static final int REASON_ASSOCIATION_TIMEOUT = 5; |
| // Other authentication failures |
| public static final int REASON_AUTHENTICATION_FAILURE = 6; |
| // DHCP failures |
| public static final int REASON_DHCP_FAILURE = 7; |
| // Abnormal disconnect error |
| public static final int REASON_ABNORMAL_DISCONNECT = 8; |
| // AP initiated disconnect for a given duration. |
| public static final int REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 9; |
| // Avoid connecting to the failed AP when trying to reconnect on other available candidates. |
| public static final int REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 10; |
| // The connected scorer has disconnected this network. |
| public static final int REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 11; |
| // Non-local disconnection in the middle of connecting state |
| public static final int REASON_NONLOCAL_DISCONNECT_CONNECTING = 12; |
| // Constant being used to keep track of how many failure reasons there are. |
| public static final int NUMBER_REASON_CODES = 13; |
| public static final int INVALID_REASON = -1; |
| |
| @IntDef(prefix = { "REASON_" }, value = { |
| REASON_AP_UNABLE_TO_HANDLE_NEW_STA, |
| REASON_NETWORK_VALIDATION_FAILURE, |
| REASON_WRONG_PASSWORD, |
| REASON_EAP_FAILURE, |
| REASON_ASSOCIATION_REJECTION, |
| REASON_ASSOCIATION_TIMEOUT, |
| REASON_AUTHENTICATION_FAILURE, |
| REASON_DHCP_FAILURE, |
| REASON_ABNORMAL_DISCONNECT, |
| REASON_FRAMEWORK_DISCONNECT_MBO_OCE, |
| REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, |
| REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, |
| REASON_NONLOCAL_DISCONNECT_CONNECTING |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface FailureReason {} |
| |
| // To be filled with values from the overlay. |
| private static final int[] FAILURE_COUNT_DISABLE_THRESHOLD = new int[NUMBER_REASON_CODES]; |
| private boolean mFailureCountDisableThresholdArrayInitialized = false; |
| private static final long ABNORMAL_DISCONNECT_RESET_TIME_MS = TimeUnit.HOURS.toMillis(3); |
| private static final int MIN_RSSI_DIFF_TO_UNBLOCK_BSSID = 5; |
| @VisibleForTesting |
| public static final int NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF = 5; |
| @VisibleForTesting |
| public static final long WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS = TimeUnit.HOURS.toMillis(18); |
| private static final String TAG = "WifiBlocklistMonitor"; |
| |
| private final Context mContext; |
| private final WifiLastResortWatchdog mWifiLastResortWatchdog; |
| private final WifiConnectivityHelper mConnectivityHelper; |
| private final Clock mClock; |
| private final LocalLog mLocalLog; |
| private final WifiScoreCard mWifiScoreCard; |
| private final ScoringParams mScoringParams; |
| private final WifiMetrics mWifiMetrics; |
| private final Map<Integer, BssidDisableReason> mBssidDisableReasons = |
| buildBssidDisableReasons(); |
| private final SparseArray<DisableReasonInfo> mDisableReasonInfo; |
| |
| // Map of bssid to BssidStatus |
| private Map<String, BssidStatus> mBssidStatusMap = new ArrayMap<>(); |
| private Set<String> mDisabledSsids = new ArraySet<>(); |
| |
| // Internal logger to make sure imporatant logs do not get lost. |
| private BssidBlocklistMonitorLogger mBssidBlocklistMonitorLogger = |
| new BssidBlocklistMonitorLogger(60); |
| |
| // Map of ssid to Allowlist SSIDs |
| private Map<String, List<String>> mSsidAllowlistMap = new ArrayMap<>(); |
| |
| /** |
| * Verbose logging flag. Toggled by developer options. |
| */ |
| private boolean mVerboseLoggingEnabled = false; |
| |
| |
| private Map<Integer, BssidDisableReason> buildBssidDisableReasons() { |
| Map<Integer, BssidDisableReason> result = new ArrayMap<>(); |
| result.put(REASON_AP_UNABLE_TO_HANDLE_NEW_STA, new BssidDisableReason( |
| "REASON_AP_UNABLE_TO_HANDLE_NEW_STA", false, false)); |
| result.put(REASON_NETWORK_VALIDATION_FAILURE, new BssidDisableReason( |
| "REASON_NETWORK_VALIDATION_FAILURE", true, false)); |
| result.put(REASON_WRONG_PASSWORD, new BssidDisableReason( |
| "REASON_WRONG_PASSWORD", false, true)); |
| result.put(REASON_EAP_FAILURE, new BssidDisableReason( |
| "REASON_EAP_FAILURE", true, true)); |
| result.put(REASON_ASSOCIATION_REJECTION, new BssidDisableReason( |
| "REASON_ASSOCIATION_REJECTION", true, true)); |
| result.put(REASON_ASSOCIATION_TIMEOUT, new BssidDisableReason( |
| "REASON_ASSOCIATION_TIMEOUT", true, true)); |
| result.put(REASON_AUTHENTICATION_FAILURE, new BssidDisableReason( |
| "REASON_AUTHENTICATION_FAILURE", true, true)); |
| result.put(REASON_DHCP_FAILURE, new BssidDisableReason( |
| "REASON_DHCP_FAILURE", true, false)); |
| result.put(REASON_ABNORMAL_DISCONNECT, new BssidDisableReason( |
| "REASON_ABNORMAL_DISCONNECT", true, false)); |
| result.put(REASON_FRAMEWORK_DISCONNECT_MBO_OCE, new BssidDisableReason( |
| "REASON_FRAMEWORK_DISCONNECT_MBO_OCE", false, false)); |
| result.put(REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT, new BssidDisableReason( |
| "REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT", false, false)); |
| result.put(REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE, new BssidDisableReason( |
| "REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE", true, false)); |
| // TODO: b/174166637, add the same reason code in SSID blocklist and mark ignoreIfOnlyBssid |
| // to true once it is covered in SSID blocklist. |
| result.put(REASON_NONLOCAL_DISCONNECT_CONNECTING, new BssidDisableReason( |
| "REASON_NONLOCAL_DISCONNECT_CONNECTING", true, false)); |
| return result; |
| } |
| |
| class BssidDisableReason { |
| public final String reasonString; |
| public final boolean isLowRssiSensitive; |
| public final boolean ignoreIfOnlyBssid; |
| |
| BssidDisableReason(String reasonString, boolean isLowRssiSensitive, |
| boolean ignoreIfOnlyBssid) { |
| this.reasonString = reasonString; |
| this.isLowRssiSensitive = isLowRssiSensitive; |
| this.ignoreIfOnlyBssid = ignoreIfOnlyBssid; |
| } |
| } |
| |
| /** |
| * Create a new instance of WifiBlocklistMonitor |
| */ |
| WifiBlocklistMonitor(Context context, WifiConnectivityHelper connectivityHelper, |
| WifiLastResortWatchdog wifiLastResortWatchdog, Clock clock, LocalLog localLog, |
| WifiScoreCard wifiScoreCard, ScoringParams scoringParams, WifiMetrics wifiMetrics) { |
| mContext = context; |
| mConnectivityHelper = connectivityHelper; |
| mWifiLastResortWatchdog = wifiLastResortWatchdog; |
| mClock = clock; |
| mLocalLog = localLog; |
| mWifiScoreCard = wifiScoreCard; |
| mScoringParams = scoringParams; |
| mDisableReasonInfo = DISABLE_REASON_INFOS.clone(); |
| mWifiMetrics = wifiMetrics; |
| loadCustomConfigsForDisableReasonInfos(); |
| } |
| |
| // A helper to log debugging information in the local log buffer, which can |
| // be retrieved in bugreport. |
| private void localLog(String log) { |
| mLocalLog.log(log); |
| } |
| |
| /** |
| * calculates the blocklist duration based on the current failure streak with exponential |
| * backoff. |
| * @param failureStreak should be greater or equal to 0. |
| * @return duration to block the BSSID in milliseconds |
| */ |
| private long getBlocklistDurationWithExponentialBackoff(int failureStreak, |
| int baseBlocklistDurationMs) { |
| failureStreak = Math.min(failureStreak, mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorFailureStreakCap)); |
| if (failureStreak < 1) { |
| return baseBlocklistDurationMs; |
| } |
| return (long) (Math.pow(2.0, (double) failureStreak) * baseBlocklistDurationMs); |
| } |
| |
| /** |
| * Dump the local log buffer and other internal state of WifiBlocklistMonitor. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of WifiBlocklistMonitor"); |
| mLocalLog.dump(fd, pw, args); |
| pw.println("WifiBlocklistMonitor - Bssid blocklist begin ----"); |
| mBssidStatusMap.values().stream().forEach(entry -> pw.println(entry)); |
| pw.println("WifiBlocklistMonitor - Bssid blocklist end ----"); |
| mBssidBlocklistMonitorLogger.dump(pw); |
| } |
| |
| private void addToBlocklist(@NonNull BssidStatus entry, long durationMs, |
| @FailureReason int reason, int rssi) { |
| entry.setAsBlocked(durationMs, reason, rssi); |
| localLog(TAG + " addToBlocklist: bssid=" + entry.bssid + ", ssid=" + entry.ssid |
| + ", durationMs=" + durationMs + ", reason=" + getFailureReasonString(reason) |
| + ", rssi=" + rssi); |
| } |
| |
| /** |
| * increments the number of failures for the given bssid and returns the number of failures so |
| * far. |
| * @return the BssidStatus for the BSSID |
| */ |
| private @NonNull BssidStatus incrementFailureCountForBssid( |
| @NonNull String bssid, @NonNull String ssid, int reasonCode) { |
| BssidStatus status = getOrCreateBssidStatus(bssid, ssid); |
| status.incrementFailureCount(reasonCode); |
| return status; |
| } |
| |
| /** |
| * Get the BssidStatus representing the BSSID or create a new one if it doesn't exist. |
| */ |
| private @NonNull BssidStatus getOrCreateBssidStatus(@NonNull String bssid, |
| @NonNull String ssid) { |
| BssidStatus status = mBssidStatusMap.get(bssid); |
| if (status == null || !ssid.equals(status.ssid)) { |
| if (status != null) { |
| localLog("getOrCreateBssidStatus: BSSID=" + bssid + ", SSID changed from " |
| + status.ssid + " to " + ssid); |
| } |
| status = new BssidStatus(bssid, ssid); |
| mBssidStatusMap.put(bssid, status); |
| } |
| return status; |
| } |
| |
| private boolean isValidNetworkAndFailureReason(String bssid, String ssid, |
| @FailureReason int reasonCode) { |
| if (bssid == null || ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) |
| || bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY) |
| || reasonCode < 0 || reasonCode >= NUMBER_REASON_CODES) { |
| Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid |
| + ", reasonCode=" + reasonCode); |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean shouldWaitForWatchdogToTriggerFirst(String bssid, |
| @FailureReason int reasonCode) { |
| boolean isWatchdogRelatedFailure = reasonCode == REASON_ASSOCIATION_REJECTION |
| || reasonCode == REASON_AUTHENTICATION_FAILURE |
| || reasonCode == REASON_DHCP_FAILURE; |
| return isWatchdogRelatedFailure && mWifiLastResortWatchdog.shouldIgnoreBssidUpdate(bssid); |
| } |
| |
| /** |
| * Block any attempts to auto-connect to the BSSID for the specified duration. |
| * This is meant to be used by features that need wifi to avoid a BSSID for a certain duration, |
| * and thus will not increase the failure streak counters. |
| * @param bssid identifies the AP to block. |
| * @param ssid identifies the SSID the AP belongs to. |
| * @param durationMs duration in millis to block. |
| * @param blockReason reason for blocking the BSSID. |
| * @param rssi the latest RSSI observed. |
| */ |
| public void blockBssidForDurationMs(@NonNull String bssid, @NonNull String ssid, |
| long durationMs, @FailureReason int blockReason, int rssi) { |
| if (durationMs <= 0 || !isValidNetworkAndFailureReason(bssid, ssid, blockReason)) { |
| Log.e(TAG, "Invalid input: BSSID=" + bssid + ", SSID=" + ssid |
| + ", durationMs=" + durationMs + ", blockReason=" + blockReason |
| + ", rssi=" + rssi); |
| return; |
| } |
| BssidStatus status = getOrCreateBssidStatus(bssid, ssid); |
| if (status.isInBlocklist |
| && status.blocklistEndTimeMs - mClock.getWallClockMillis() > durationMs) { |
| // Return because this BSSID is already being blocked for a longer time. |
| return; |
| } |
| addToBlocklist(status, durationMs, blockReason, rssi); |
| } |
| |
| private String getFailureReasonString(@FailureReason int reasonCode) { |
| if (reasonCode == INVALID_REASON) { |
| return "INVALID_REASON"; |
| } |
| BssidDisableReason disableReason = mBssidDisableReasons.get(reasonCode); |
| if (disableReason == null) { |
| return "REASON_UNKNOWN"; |
| } |
| return disableReason.reasonString; |
| } |
| |
| private int getFailureThresholdForReason(@FailureReason int reasonCode) { |
| if (mFailureCountDisableThresholdArrayInitialized) { |
| return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; |
| } |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorApUnableToHandleNewStaThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NETWORK_VALIDATION_FAILURE] = |
| mContext.getResources().getInteger(R.integer |
| .config_wifiBssidBlocklistMonitorNetworkValidationFailureThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_WRONG_PASSWORD] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorWrongPasswordThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_EAP_FAILURE] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorEapFailureThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_REJECTION] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorAssociationRejectionThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ASSOCIATION_TIMEOUT] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorAssociationTimeoutThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_AUTHENTICATION_FAILURE] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorAuthenticationFailureThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_DHCP_FAILURE] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorDhcpFailureThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_ABNORMAL_DISCONNECT] = |
| mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorAbnormalDisconnectThreshold); |
| FAILURE_COUNT_DISABLE_THRESHOLD[REASON_NONLOCAL_DISCONNECT_CONNECTING] = |
| mContext.getResources().getInteger(R.integer |
| .config_wifiBssidBlocklistMonitorNonlocalDisconnectConnectingThreshold); |
| mFailureCountDisableThresholdArrayInitialized = true; |
| return FAILURE_COUNT_DISABLE_THRESHOLD[reasonCode]; |
| } |
| |
| private boolean handleBssidConnectionFailureInternal(String bssid, String ssid, |
| @FailureReason int reasonCode, int rssi) { |
| BssidStatus entry = incrementFailureCountForBssid(bssid, ssid, reasonCode); |
| int failureThreshold = getFailureThresholdForReason(reasonCode); |
| int currentStreak = mWifiScoreCard.getBssidBlocklistStreak(ssid, bssid, reasonCode); |
| if (currentStreak > 0 || entry.failureCount[reasonCode] >= failureThreshold) { |
| // To rule out potential device side issues, don't add to blocklist if |
| // WifiLastResortWatchdog is still not triggered |
| if (shouldWaitForWatchdogToTriggerFirst(bssid, reasonCode)) { |
| localLog("Ignoring failure to wait for watchdog to trigger first."); |
| return false; |
| } |
| int baseBlockDurationMs = getBaseBlockDurationForReason(reasonCode); |
| addToBlocklist(entry, |
| getBlocklistDurationWithExponentialBackoff(currentStreak, baseBlockDurationMs), |
| reasonCode, rssi); |
| mWifiScoreCard.incrementBssidBlocklistStreak(ssid, bssid, reasonCode); |
| return true; |
| } |
| return false; |
| } |
| |
| private int getBaseBlockDurationForReason(int blockReason) { |
| switch (blockReason) { |
| case REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE: |
| return mContext.getResources().getInteger(R.integer |
| .config_wifiBssidBlocklistMonitorConnectedScoreBaseBlockDurationMs); |
| default: |
| return mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistMonitorBaseBlockDurationMs); |
| } |
| } |
| |
| /** |
| * Note a failure event on a bssid and perform appropriate actions. |
| * @return True if the blocklist has been modified. |
| */ |
| public boolean handleBssidConnectionFailure(String bssid, String ssid, |
| @FailureReason int reasonCode, int rssi) { |
| if (!isValidNetworkAndFailureReason(bssid, ssid, reasonCode)) { |
| return false; |
| } |
| BssidDisableReason bssidDisableReason = mBssidDisableReasons.get(reasonCode); |
| if (bssidDisableReason == null) { |
| Log.e(TAG, "Bssid disable reason not found. ReasonCode=" + reasonCode); |
| return false; |
| } |
| if (bssidDisableReason.ignoreIfOnlyBssid && !mDisabledSsids.contains(ssid) |
| && mWifiLastResortWatchdog.isBssidOnlyApOfSsid(bssid)) { |
| localLog("Ignoring BSSID failure due to no other APs available. BSSID=" + bssid); |
| return false; |
| } |
| if (reasonCode == REASON_ABNORMAL_DISCONNECT) { |
| long connectionTime = mWifiScoreCard.getBssidConnectionTimestampMs(ssid, bssid); |
| // only count disconnects that happen shortly after a connection. |
| if (mClock.getWallClockMillis() - connectionTime |
| > mContext.getResources().getInteger( |
| R.integer.config_wifiBssidBlocklistAbnormalDisconnectTimeWindowMs)) { |
| return false; |
| } |
| } |
| return handleBssidConnectionFailureInternal(bssid, ssid, reasonCode, rssi); |
| } |
| |
| /** |
| * To be called when a WifiConfiguration is either temporarily disabled or permanently disabled. |
| * @param ssid of the WifiConfiguration that is disabled. |
| */ |
| public void handleWifiConfigurationDisabled(String ssid) { |
| if (ssid != null) { |
| mDisabledSsids.add(ssid); |
| } |
| } |
| |
| /** |
| * Note a connection success event on a bssid and clear appropriate failure counters. |
| */ |
| public void handleBssidConnectionSuccess(@NonNull String bssid, @NonNull String ssid) { |
| mDisabledSsids.remove(ssid); |
| /** |
| * First reset the blocklist streak. |
| * This needs to be done even if a BssidStatus is not found, since the BssidStatus may |
| * have been removed due to blocklist timeout. |
| */ |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AP_UNABLE_TO_HANDLE_NEW_STA); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_WRONG_PASSWORD); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_EAP_FAILURE); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_REJECTION); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ASSOCIATION_TIMEOUT); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_AUTHENTICATION_FAILURE); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, |
| REASON_NONLOCAL_DISCONNECT_CONNECTING); |
| |
| long connectionTime = mClock.getWallClockMillis(); |
| long prevConnectionTime = mWifiScoreCard.setBssidConnectionTimestampMs( |
| ssid, bssid, connectionTime); |
| if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_ABNORMAL_DISCONNECT); |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, |
| REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE); |
| } |
| |
| BssidStatus status = mBssidStatusMap.get(bssid); |
| if (status == null) { |
| return; |
| } |
| // Clear the L2 failure counters |
| status.failureCount[REASON_AP_UNABLE_TO_HANDLE_NEW_STA] = 0; |
| status.failureCount[REASON_WRONG_PASSWORD] = 0; |
| status.failureCount[REASON_EAP_FAILURE] = 0; |
| status.failureCount[REASON_ASSOCIATION_REJECTION] = 0; |
| status.failureCount[REASON_ASSOCIATION_TIMEOUT] = 0; |
| status.failureCount[REASON_AUTHENTICATION_FAILURE] = 0; |
| status.failureCount[REASON_NONLOCAL_DISCONNECT_CONNECTING] = 0; |
| if (connectionTime - prevConnectionTime > ABNORMAL_DISCONNECT_RESET_TIME_MS) { |
| status.failureCount[REASON_ABNORMAL_DISCONNECT] = 0; |
| } |
| } |
| |
| /** |
| * Note a successful network validation on a BSSID and clear appropriate failure counters. |
| * And then remove the BSSID from blocklist. |
| */ |
| public void handleNetworkValidationSuccess(@NonNull String bssid, @NonNull String ssid) { |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_NETWORK_VALIDATION_FAILURE); |
| BssidStatus status = mBssidStatusMap.get(bssid); |
| if (status == null) { |
| return; |
| } |
| status.failureCount[REASON_NETWORK_VALIDATION_FAILURE] = 0; |
| /** |
| * Network validation may take more than 1 tries to succeed. |
| * remove the BSSID from blocklist to make sure we are not accidentally blocking good |
| * BSSIDs. |
| **/ |
| if (status.isInBlocklist) { |
| mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "Network validation success"); |
| mBssidStatusMap.remove(bssid); |
| } |
| } |
| |
| /** |
| * Note a successful DHCP provisioning and clear appropriate faliure counters. |
| */ |
| public void handleDhcpProvisioningSuccess(@NonNull String bssid, @NonNull String ssid) { |
| mWifiScoreCard.resetBssidBlocklistStreak(ssid, bssid, REASON_DHCP_FAILURE); |
| BssidStatus status = mBssidStatusMap.get(bssid); |
| if (status == null) { |
| return; |
| } |
| status.failureCount[REASON_DHCP_FAILURE] = 0; |
| } |
| |
| /** |
| * Note the removal of a network from the Wifi stack's internal database and reset |
| * appropriate failure counters. |
| * @param ssid |
| */ |
| public void handleNetworkRemoved(@NonNull String ssid) { |
| clearBssidBlocklistForSsid(ssid); |
| mWifiScoreCard.resetBssidBlocklistStreakForSsid(ssid); |
| } |
| |
| /** |
| * Clears the blocklist for BSSIDs associated with the input SSID only. |
| * @param ssid |
| */ |
| public void clearBssidBlocklistForSsid(@NonNull String ssid) { |
| int prevSize = mBssidStatusMap.size(); |
| mBssidStatusMap.entrySet().removeIf(e -> { |
| BssidStatus status = e.getValue(); |
| if (status.ssid == null) { |
| return false; |
| } |
| if (status.ssid.equals(ssid)) { |
| mBssidBlocklistMonitorLogger.logBssidUnblocked( |
| status, "clearBssidBlocklistForSsid"); |
| return true; |
| } |
| return false; |
| }); |
| int diff = prevSize - mBssidStatusMap.size(); |
| if (diff > 0) { |
| localLog(TAG + " clearBssidBlocklistForSsid: SSID=" + ssid |
| + ", num BSSIDs cleared=" + diff); |
| } |
| } |
| |
| /** |
| * Clears the BSSID blocklist and failure counters. |
| */ |
| public void clearBssidBlocklist() { |
| if (mBssidStatusMap.size() > 0) { |
| int prevSize = mBssidStatusMap.size(); |
| for (BssidStatus status : mBssidStatusMap.values()) { |
| mBssidBlocklistMonitorLogger.logBssidUnblocked(status, "clearBssidBlocklist"); |
| } |
| mBssidStatusMap.clear(); |
| localLog(TAG + " clearBssidBlocklist: num BSSIDs cleared=" |
| + (prevSize - mBssidStatusMap.size())); |
| } |
| mDisabledSsids.clear(); |
| } |
| |
| /** |
| * @param ssid |
| * @return the number of BSSIDs currently in the blocklist for the |ssid|. |
| */ |
| public int updateAndGetNumBlockedBssidsForSsid(@NonNull String ssid) { |
| return (int) updateAndGetBssidBlocklistInternal() |
| .filter(entry -> ssid.equals(entry.ssid)).count(); |
| } |
| |
| private int getNumBlockedBssidsForSsids(@NonNull Set<String> ssids) { |
| if (ssids.isEmpty()) { |
| return 0; |
| } |
| return (int) mBssidStatusMap.values().stream() |
| .filter(entry -> entry.isInBlocklist && ssids.contains(entry.ssid)) |
| .count(); |
| } |
| |
| /** |
| * Overloaded version of updateAndGetBssidBlocklist. |
| * Accepts a @Nullable String ssid as input, and updates the firmware roaming |
| * configuration if the blocklist for the input ssid has been changed. |
| * @param ssids set of ssids to update firmware roaming configuration for. |
| * @return Set of BSSIDs currently in the blocklist |
| */ |
| public Set<String> updateAndGetBssidBlocklistForSsids(@NonNull Set<String> ssids) { |
| int numBefore = getNumBlockedBssidsForSsids(ssids); |
| Set<String> bssidBlocklist = updateAndGetBssidBlocklist(); |
| if (getNumBlockedBssidsForSsids(ssids) != numBefore) { |
| updateFirmwareRoamingConfiguration(ssids); |
| } |
| return bssidBlocklist; |
| } |
| |
| /** |
| * Gets the BSSIDs that are currently in the blocklist. |
| * @return Set of BSSIDs currently in the blocklist |
| */ |
| public Set<String> updateAndGetBssidBlocklist() { |
| return updateAndGetBssidBlocklistInternal() |
| .map(entry -> entry.bssid) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** |
| * Gets the list of block reasons for BSSIDs currently in the blocklist. |
| * @return The set of unique reasons for blocking BSSIDs with this SSID. |
| */ |
| public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) { |
| if (ssid == null) { |
| return Collections.emptySet(); |
| } |
| return mBssidStatusMap.values().stream() |
| .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid)) |
| .map(entry -> entry.blockReason) |
| .collect(Collectors.toSet()); |
| } |
| |
| /** |
| * Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI. |
| * @param scanDetails |
| */ |
| public void tryEnablingBlockedBssids(List<ScanDetail> scanDetails) { |
| if (scanDetails == null) { |
| return; |
| } |
| for (ScanDetail scanDetail : scanDetails) { |
| ScanResult scanResult = scanDetail.getScanResult(); |
| if (scanResult == null) { |
| continue; |
| } |
| BssidStatus status = mBssidStatusMap.get(scanResult.BSSID); |
| if (status == null || !status.isInBlocklist |
| || !isLowRssiSensitiveFailure(status.blockReason)) { |
| continue; |
| } |
| int sufficientRssi = mScoringParams.getSufficientRssi(scanResult.frequency); |
| if (status.lastRssi < sufficientRssi && scanResult.level >= sufficientRssi |
| && scanResult.level - status.lastRssi >= MIN_RSSI_DIFF_TO_UNBLOCK_BSSID) { |
| mBssidBlocklistMonitorLogger.logBssidUnblocked( |
| status, "rssi significantly improved"); |
| mBssidStatusMap.remove(status.bssid); |
| } |
| } |
| } |
| |
| private boolean isLowRssiSensitiveFailure(int blockReason) { |
| return mBssidDisableReasons.get(blockReason) == null ? false |
| : mBssidDisableReasons.get(blockReason).isLowRssiSensitive; |
| } |
| |
| /** |
| * Removes expired BssidStatus entries and then return remaining entries in the blocklist. |
| * @return Stream of BssidStatus for BSSIDs that are in the blocklist. |
| */ |
| private Stream<BssidStatus> updateAndGetBssidBlocklistInternal() { |
| Stream.Builder<BssidStatus> builder = Stream.builder(); |
| long curTime = mClock.getWallClockMillis(); |
| mBssidStatusMap.entrySet().removeIf(e -> { |
| BssidStatus status = e.getValue(); |
| if (status.isInBlocklist) { |
| if (status.blocklistEndTimeMs < curTime) { |
| mBssidBlocklistMonitorLogger.logBssidUnblocked( |
| status, "updateAndGetBssidBlocklistInternal"); |
| return true; |
| } |
| builder.accept(status); |
| } |
| return false; |
| }); |
| return builder.build(); |
| } |
| |
| /** |
| * Sends the BSSIDs belonging to the input SSID down to the firmware to prevent auto-roaming |
| * to those BSSIDs. |
| * @param ssids |
| */ |
| public void updateFirmwareRoamingConfiguration(@NonNull Set<String> ssids) { |
| if (!mConnectivityHelper.isFirmwareRoamingSupported()) { |
| return; |
| } |
| ArrayList<String> bssidBlocklist = updateAndGetBssidBlocklistInternal() |
| .filter(entry -> ssids.contains(entry.ssid)) |
| .sorted((o1, o2) -> (int) (o2.blocklistEndTimeMs - o1.blocklistEndTimeMs)) |
| .map(entry -> entry.bssid) |
| .collect(Collectors.toCollection(ArrayList::new)); |
| int fwMaxBlocklistSize = mConnectivityHelper.getMaxNumBlocklistBssid(); |
| if (fwMaxBlocklistSize <= 0) { |
| Log.e(TAG, "Invalid max BSSID blocklist size: " + fwMaxBlocklistSize); |
| return; |
| } |
| // Having the blocklist size exceeding firmware max limit is unlikely because we have |
| // already flitered based on SSID. But just in case this happens, we are prioritizing |
| // sending down BSSIDs blocked for the longest time. |
| if (bssidBlocklist.size() > fwMaxBlocklistSize) { |
| bssidBlocklist = new ArrayList<String>(bssidBlocklist.subList(0, |
| fwMaxBlocklistSize)); |
| } |
| |
| // Collect all the allowed SSIDs |
| Set<String> allowedSsidSet = new HashSet<>(); |
| for (String ssid : ssids) { |
| List<String> allowedSsidsForSsid = mSsidAllowlistMap.get(ssid); |
| if (allowedSsidsForSsid != null) { |
| allowedSsidSet.addAll(allowedSsidsForSsid); |
| } |
| } |
| ArrayList<String> ssidAllowlist = new ArrayList<>(allowedSsidSet); |
| int allowlistSize = ssidAllowlist.size(); |
| int maxAllowlistSize = mConnectivityHelper.getMaxNumAllowlistSsid(); |
| if (maxAllowlistSize <= 0) { |
| Log.wtf(TAG, "Invalid max SSID allowlist size: " + maxAllowlistSize); |
| return; |
| } |
| if (allowlistSize > maxAllowlistSize) { |
| ssidAllowlist = new ArrayList<>(ssidAllowlist.subList(0, maxAllowlistSize)); |
| localLog("Trim down SSID allowlist size from " + allowlistSize + " to " |
| + ssidAllowlist.size()); |
| } |
| |
| // plumb down to HAL |
| String message = "set firmware roaming configurations. " |
| + "bssidBlocklist="; |
| if (bssidBlocklist.size() == 0) { |
| message += "<EMPTY>"; |
| } else { |
| message += String.join(", ", bssidBlocklist); |
| } |
| if (!mConnectivityHelper.setFirmwareRoamingConfiguration(bssidBlocklist, ssidAllowlist)) { |
| Log.e(TAG, "Failed to " + message); |
| mBssidBlocklistMonitorLogger.log("Failed to " + message); |
| } else { |
| mBssidBlocklistMonitorLogger.log("Successfully " + message); |
| } |
| } |
| |
| @VisibleForTesting |
| public int getBssidBlocklistMonitorLoggerSize() { |
| return mBssidBlocklistMonitorLogger.size(); |
| } |
| |
| private class BssidBlocklistMonitorLogger { |
| private LinkedList<String> mLogBuffer = new LinkedList<>(); |
| private int mBufferSize; |
| |
| BssidBlocklistMonitorLogger(int bufferSize) { |
| mBufferSize = bufferSize; |
| } |
| |
| public void logBssidUnblocked(BssidStatus bssidStatus, String unblockReason) { |
| // only log history for Bssids that had been blocked. |
| if (bssidStatus == null || !bssidStatus.isInBlocklist) { |
| return; |
| } |
| StringBuilder sb = createStringBuilderWithLogTime(); |
| sb.append(", Bssid unblocked, Reason=" + unblockReason); |
| sb.append(", Unblocked BssidStatus={" + bssidStatus.toString() + "}"); |
| logInternal(sb.toString()); |
| } |
| |
| // cache a single line of log message in the rotating buffer |
| public void log(String message) { |
| if (message == null) { |
| return; |
| } |
| StringBuilder sb = createStringBuilderWithLogTime(); |
| sb.append(" " + message); |
| logInternal(sb.toString()); |
| } |
| |
| private StringBuilder createStringBuilderWithLogTime() { |
| StringBuilder sb = new StringBuilder(); |
| Calendar calendar = Calendar.getInstance(); |
| calendar.setTimeInMillis(mClock.getWallClockMillis()); |
| sb.append("logTimeMs=" + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar, |
| calendar, calendar, calendar, calendar)); |
| return sb; |
| } |
| |
| private void logInternal(String message) { |
| mLogBuffer.add(message); |
| if (mLogBuffer.size() > mBufferSize) { |
| mLogBuffer.removeFirst(); |
| } |
| } |
| |
| @VisibleForTesting |
| public int size() { |
| return mLogBuffer.size(); |
| } |
| |
| public void dump(PrintWriter pw) { |
| pw.println("WifiBlocklistMonitor - Bssid blocklist logs begin ----"); |
| for (String line : mLogBuffer) { |
| pw.println(line); |
| } |
| pw.println("WifiBlocklistMonitor - Bssid blocklist logs end ----"); |
| } |
| } |
| |
| /** |
| * Helper class that counts the number of failures per BSSID. |
| */ |
| private class BssidStatus { |
| public final String bssid; |
| public final String ssid; |
| public final int[] failureCount = new int[NUMBER_REASON_CODES]; |
| public int blockReason = INVALID_REASON; // reason of blocking this BSSID |
| // The latest RSSI that's seen before this BSSID is added to blocklist. |
| public int lastRssi = 0; |
| |
| // The following are used to flag how long this BSSID stays in the blocklist. |
| public boolean isInBlocklist; |
| public long blocklistEndTimeMs; |
| public long blocklistStartTimeMs; |
| |
| BssidStatus(String bssid, String ssid) { |
| this.bssid = bssid; |
| this.ssid = ssid; |
| } |
| |
| /** |
| * increments the failure count for the reasonCode by 1. |
| * @return the incremented failure count |
| */ |
| public int incrementFailureCount(int reasonCode) { |
| return ++failureCount[reasonCode]; |
| } |
| |
| /** |
| * Set this BSSID as blocked for the specified duration. |
| * @param durationMs |
| * @param blockReason |
| * @param rssi |
| */ |
| public void setAsBlocked(long durationMs, @FailureReason int blockReason, int rssi) { |
| isInBlocklist = true; |
| blocklistStartTimeMs = mClock.getWallClockMillis(); |
| blocklistEndTimeMs = blocklistStartTimeMs + durationMs; |
| this.blockReason = blockReason; |
| lastRssi = rssi; |
| mWifiMetrics.incrementBssidBlocklistCount(blockReason); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("BSSID=" + bssid); |
| sb.append(", SSID=" + ssid); |
| sb.append(", isInBlocklist=" + isInBlocklist); |
| if (isInBlocklist) { |
| sb.append(", blockReason=" + getFailureReasonString(blockReason)); |
| sb.append(", lastRssi=" + lastRssi); |
| Calendar calendar = Calendar.getInstance(); |
| calendar.setTimeInMillis(blocklistStartTimeMs); |
| sb.append(", blocklistStartTimeMs=" |
| + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar, |
| calendar, calendar, calendar, calendar)); |
| calendar.setTimeInMillis(blocklistEndTimeMs); |
| sb.append(", blocklistEndTimeMs=" |
| + String.format("%tm-%td %tH:%tM:%tS.%tL", calendar, calendar, |
| calendar, calendar, calendar, calendar)); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Enable/disable verbose logging in WifiBlocklistMonitor. |
| */ |
| public void enableVerboseLogging(boolean verbose) { |
| mVerboseLoggingEnabled = verbose; |
| } |
| |
| /** |
| * Modify the internal copy of DisableReasonInfo with custom configurations defined in |
| * an overlay. |
| */ |
| private void loadCustomConfigsForDisableReasonInfos() { |
| mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION, |
| new DisableReasonInfo( |
| // Note that there is a space at the end of this string. Cannot fix |
| // since this string is persisted. |
| "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ", |
| mContext.getResources().getInteger(R.integer |
| .config_wifiDisableReasonAssociationRejectionThreshold), |
| 5 * 60 * 1000)); |
| |
| mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE, |
| new DisableReasonInfo( |
| "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE", |
| mContext.getResources().getInteger(R.integer |
| .config_wifiDisableReasonAuthenticationFailureThreshold), |
| 5 * 60 * 1000)); |
| |
| mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_DHCP_FAILURE, |
| new DisableReasonInfo( |
| "NETWORK_SELECTION_DISABLED_DHCP_FAILURE", |
| mContext.getResources().getInteger(R.integer |
| .config_wifiDisableReasonDhcpFailureThreshold), |
| 5 * 60 * 1000)); |
| |
| mDisableReasonInfo.put(NetworkSelectionStatus.DISABLED_NETWORK_NOT_FOUND, |
| new DisableReasonInfo( |
| "NETWORK_SELECTION_DISABLED_NETWORK_NOT_FOUND", |
| mContext.getResources().getInteger(R.integer |
| .config_wifiDisableReasonNetworkNotFoundThreshold), |
| 5 * 60 * 1000)); |
| } |
| |
| /** Update DisableReasonInfo with carrier configurations defined in an overlay. **/ |
| public void loadCarrierConfigsForDisableReasonInfos() { |
| int duration = mContext.getResources().getInteger( |
| R.integer.config_wifiDisableReasonAuthenticationFailureCarrierSpecificDurationMs); |
| DisableReasonInfo disableReasonInfo = new DisableReasonInfo( |
| "NETWORK_SELECTION_DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR", |
| mContext.getResources().getInteger(R.integer |
| .config_wifiDisableReasonAuthenticationFailureCarrierSpecificThreshold), |
| duration); |
| mDisableReasonInfo.put( |
| NetworkSelectionStatus.DISABLED_AUTHENTICATION_PRIVATE_EAP_ERROR, |
| disableReasonInfo); |
| } |
| |
| /** |
| * Returns true if the disable duration for this WifiConfiguration has passed. Returns false |
| * if the WifiConfiguration is either not disabled or is permanently disabled. |
| */ |
| public boolean shouldEnableNetwork(WifiConfiguration config) { |
| NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); |
| if (networkStatus.isNetworkTemporaryDisabled()) { |
| long timeDifferenceMs = |
| mClock.getElapsedSinceBootMillis() - networkStatus.getDisableTime(); |
| int disableReason = networkStatus.getNetworkSelectionDisableReason(); |
| long disableTimeoutMs = (long) getNetworkSelectionDisableTimeoutMillis(disableReason); |
| int exponentialBackoffCount = mWifiScoreCard.lookupNetwork(config.SSID) |
| .getRecentStats().getCount(WifiScoreCard.CNT_CONSECUTIVE_CONNECTION_FAILURE) |
| - NUM_CONSECUTIVE_FAILURES_PER_NETWORK_EXP_BACKOFF; |
| for (int i = 0; i < exponentialBackoffCount; i++) { |
| disableTimeoutMs *= 2; |
| if (disableTimeoutMs > WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS) { |
| disableTimeoutMs = WIFI_CONFIG_MAX_DISABLE_DURATION_MILLIS; |
| break; |
| } |
| } |
| if (timeDifferenceMs >= disableTimeoutMs) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Update a network's status (both internal and public) according to the update reason and |
| * its current state. This method is expects to directly modify the internal WifiConfiguration |
| * that is stored by WifiConfigManager. |
| * |
| * @param config the internal WifiConfiguration to be updated. |
| * @param reason reason code for update. |
| * @return true if the input configuration has been updated, false otherwise. |
| */ |
| public boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) { |
| if (reason < 0 || reason >= NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX) { |
| Log.e(TAG, "Invalid Network disable reason " + reason); |
| return false; |
| } |
| NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); |
| if (reason != NetworkSelectionStatus.DISABLED_NONE) { |
| // Do not update SSID blocklist with information if this is the only |
| // SSID be observed. By ignoring it we will cause additional failures |
| // which will trigger Watchdog. |
| if (reason == NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION |
| || reason == NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE |
| || reason == NetworkSelectionStatus.DISABLED_DHCP_FAILURE) { |
| if (mWifiLastResortWatchdog.shouldIgnoreSsidUpdate()) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Ignore update network selection status " |
| + "since Watchdog trigger is activated"); |
| } |
| return false; |
| } |
| } |
| |
| networkStatus.incrementDisableReasonCounter(reason); |
| // For network disable reasons, we should only update the status if we cross the |
| // threshold. |
| int disableReasonCounter = networkStatus.getDisableReasonCounter(reason); |
| int disableReasonThreshold = getNetworkSelectionDisableThreshold(reason); |
| if (disableReasonCounter < disableReasonThreshold) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Disable counter for network " + config.getPrintableSsid() |
| + " for reason " |
| + NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason) |
| + " is " + networkStatus.getDisableReasonCounter(reason) |
| + " and threshold is " + disableReasonThreshold); |
| } |
| return true; |
| } |
| } |
| setNetworkSelectionStatus(config, reason); |
| return true; |
| } |
| |
| /** |
| * Sets a network's status (both internal and public) according to the update reason and |
| * its current state. |
| * |
| * This updates the network's {@link WifiConfiguration#mNetworkSelectionStatus} field and the |
| * public {@link WifiConfiguration#status} field if the network is either enabled or |
| * permanently disabled. |
| * |
| * @param config network to be updated. |
| * @param reason reason code for update. |
| */ |
| private void setNetworkSelectionStatus(WifiConfiguration config, int reason) { |
| NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus(); |
| if (reason == NetworkSelectionStatus.DISABLED_NONE) { |
| setNetworkSelectionEnabled(config); |
| } else if (getNetworkSelectionDisableTimeoutMillis(reason) |
| != DisableReasonInfo.PERMANENT_DISABLE_TIMEOUT) { |
| setNetworkSelectionTemporarilyDisabled(config, reason); |
| } else { |
| setNetworkSelectionPermanentlyDisabled(config, reason); |
| } |
| localLog("setNetworkSelectionStatus: configKey=" + config.getProfileKey() |
| + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason=" |
| + networkStatus.getNetworkSelectionDisableReasonString()); |
| } |
| |
| /** |
| * Helper method to mark a network enabled for network selection. |
| */ |
| private void setNetworkSelectionEnabled(WifiConfiguration config) { |
| NetworkSelectionStatus status = config.getNetworkSelectionStatus(); |
| if (status.getNetworkSelectionStatus() |
| != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) { |
| localLog("setNetworkSelectionEnabled: configKey=" + config.getProfileKey() |
| + " old networkStatus=" + status.getNetworkStatusString() |
| + " disableReason=" + status.getNetworkSelectionDisableReasonString()); |
| } |
| status.setNetworkSelectionStatus( |
| NetworkSelectionStatus.NETWORK_SELECTION_ENABLED); |
| status.setDisableTime( |
| NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); |
| status.setNetworkSelectionDisableReason(NetworkSelectionStatus.DISABLED_NONE); |
| |
| // Clear out all the disable reason counters. |
| status.clearDisableReasonCounter(); |
| config.status = WifiConfiguration.Status.ENABLED; |
| } |
| |
| /** |
| * Helper method to mark a network temporarily disabled for network selection. |
| */ |
| private void setNetworkSelectionTemporarilyDisabled( |
| WifiConfiguration config, int disableReason) { |
| NetworkSelectionStatus status = config.getNetworkSelectionStatus(); |
| status.setNetworkSelectionStatus( |
| NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED); |
| // Only need a valid time filled in for temporarily disabled networks. |
| status.setDisableTime(mClock.getElapsedSinceBootMillis()); |
| status.setNetworkSelectionDisableReason(disableReason); |
| handleWifiConfigurationDisabled(config.SSID); |
| mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason); |
| } |
| |
| /** |
| * Helper method to mark a network permanently disabled for network selection. |
| */ |
| private void setNetworkSelectionPermanentlyDisabled( |
| WifiConfiguration config, int disableReason) { |
| NetworkSelectionStatus status = config.getNetworkSelectionStatus(); |
| status.setNetworkSelectionStatus( |
| NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED); |
| status.setDisableTime( |
| NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP); |
| status.setNetworkSelectionDisableReason(disableReason); |
| handleWifiConfigurationDisabled(config.SSID); |
| config.status = WifiConfiguration.Status.DISABLED; |
| mWifiMetrics.incrementWificonfigurationBlocklistCount(disableReason); |
| } |
| |
| /** |
| * Network Selection disable reason thresholds. These numbers are used to debounce network |
| * failures before we disable them. |
| * |
| * @param reason int reason code |
| * @return the disable threshold, or -1 if not found. |
| */ |
| @VisibleForTesting |
| public int getNetworkSelectionDisableThreshold(@NetworkSelectionDisableReason int reason) { |
| DisableReasonInfo info = mDisableReasonInfo.get(reason); |
| if (info == null) { |
| Log.e(TAG, "Unrecognized network disable reason code for disable threshold: " + reason); |
| return -1; |
| } else { |
| return info.mDisableThreshold; |
| } |
| } |
| |
| /** |
| * Network Selection disable timeout for each kind of error. After the timeout in milliseconds, |
| * enable the network again. |
| */ |
| @VisibleForTesting |
| public int getNetworkSelectionDisableTimeoutMillis(@NetworkSelectionDisableReason int reason) { |
| DisableReasonInfo info = mDisableReasonInfo.get(reason); |
| if (info == null) { |
| Log.e(TAG, "Unrecognized network disable reason code for disable timeout: " + reason); |
| return -1; |
| } else { |
| return info.mDisableTimeoutMillis; |
| } |
| } |
| |
| /** |
| * Sets the allowlist ssids for the given ssid |
| */ |
| public void setAllowlistSsids(@NonNull String ssid, @NonNull List<String> ssidAllowlist) { |
| mSsidAllowlistMap.put(ssid, ssidAllowlist); |
| } |
| } |