| /* |
| * Copyright (C) 2016 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 android.content.Context; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiConfiguration; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.text.TextUtils; |
| import android.util.LocalLog; |
| import android.util.Log; |
| import android.util.Pair; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.wifi.resources.R; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * This Class is a Work-In-Progress, intended behavior is as follows: |
| * Essentially this class automates a user toggling 'Airplane Mode' when WiFi "won't work". |
| * IF each available saved network has failed connecting more times than the FAILURE_THRESHOLD |
| * THEN Watchdog will restart Supplicant, wifi driver and return ClientModeImpl to InitialState. |
| */ |
| public class WifiLastResortWatchdog { |
| private static final String TAG = "WifiLastResortWatchdog"; |
| private boolean mVerboseLoggingEnabled = false; |
| /** |
| * Association Failure code |
| */ |
| public static final int FAILURE_CODE_ASSOCIATION = 1; |
| /** |
| * Authentication Failure code |
| */ |
| public static final int FAILURE_CODE_AUTHENTICATION = 2; |
| /** |
| * Dhcp Failure code |
| */ |
| public static final int FAILURE_CODE_DHCP = 3; |
| /** |
| * Maximum number of scan results received since we last saw a BSSID. |
| * If it is not seen before this limit is reached, the network is culled |
| */ |
| public static final int MAX_BSSID_AGE = 10; |
| /** |
| * BSSID used to increment failure counts against ALL bssids associated with a particular SSID |
| */ |
| public static final String BSSID_ANY = "any"; |
| /** |
| * Failure count that each available networks must meet to possibly trigger the Watchdog |
| */ |
| public static final int FAILURE_THRESHOLD = 7; |
| public static final String BUGREPORT_TITLE = "Wifi watchdog triggered"; |
| public static final double PROB_TAKE_BUGREPORT_DEFAULT = 1; |
| |
| // Number of milliseconds to wait before re-enable Watchdog triger |
| @VisibleForTesting |
| public static final long LAST_TRIGGER_TIMEOUT_MILLIS = 2 * 3600 * 1000; // 2 hours |
| |
| |
| /** |
| * Cached WifiConfigurations of available networks seen within MAX_BSSID_AGE scan results |
| * Key:BSSID, Value:Counters of failure types |
| */ |
| private Map<String, AvailableNetworkFailureCount> mRecentAvailableNetworks = new HashMap<>(); |
| |
| /** |
| * Map of SSID to <FailureCount, AP count>, used to count failures & number of access points |
| * belonging to an SSID. |
| */ |
| private Map<String, Pair<AvailableNetworkFailureCount, Integer>> mSsidFailureCount = |
| new HashMap<>(); |
| |
| /* List of failure BSSID */ |
| private Set<String> mBssidFailureList = new HashSet<>(); |
| |
| // Tracks: if ClientModeImpl is in ConnectedState |
| private boolean mWifiIsConnected = false; |
| // Is Watchdog allowed to trigger now? Set to false after triggering. Set to true after |
| // successfully connecting or a new network (SSID) becomes available to connect to. |
| private boolean mWatchdogAllowedToTrigger = true; |
| private long mTimeLastTrigger = 0; |
| private String mSsidLastTrigger = null; |
| |
| private WifiInjector mWifiInjector; |
| private WifiMetrics mWifiMetrics; |
| private ClientModeImpl mClientModeImpl; |
| private Looper mClientModeImplLooper; |
| private double mBugReportProbability = PROB_TAKE_BUGREPORT_DEFAULT; |
| private Clock mClock; |
| private Context mContext; |
| private DeviceConfigFacade mDeviceConfigFacade; |
| // If any connection failure happened after watchdog triggering restart then assume watchdog |
| // did not fix the problem |
| private boolean mWatchdogFixedWifi = true; |
| private long mLastStartConnectTime = 0; |
| private final Handler mHandler; |
| private final WifiThreadRunner mWifiThreadRunner; |
| |
| private Boolean mWatchdogFeatureEnabled = null; |
| |
| /** |
| * Local log used for debugging any WifiLastResortWatchdog issues. |
| */ |
| private final LocalLog mLocalLog = new LocalLog(100); |
| |
| WifiLastResortWatchdog(WifiInjector wifiInjector, Context context, Clock clock, |
| WifiMetrics wifiMetrics, ClientModeImpl clientModeImpl, Looper clientModeImplLooper, |
| DeviceConfigFacade deviceConfigFacade, WifiThreadRunner wifiThreadRunner) { |
| mWifiInjector = wifiInjector; |
| mClock = clock; |
| mWifiMetrics = wifiMetrics; |
| mClientModeImpl = clientModeImpl; |
| mClientModeImplLooper = clientModeImplLooper; |
| mContext = context; |
| mDeviceConfigFacade = deviceConfigFacade; |
| mWifiThreadRunner = wifiThreadRunner; |
| mHandler = new Handler(clientModeImplLooper) { |
| public void handleMessage(Message msg) { |
| processMessage(msg); |
| } |
| }; |
| } |
| |
| /** |
| * Returns handler for L2 events from supplicant. |
| * @return Handler |
| */ |
| public Handler getHandler() { |
| return mHandler; |
| } |
| |
| /** |
| * Refreshes when the last CMD_START_CONNECT is triggered. |
| */ |
| public void noteStartConnectTime() { |
| mHandler.post(() -> { |
| mLastStartConnectTime = mClock.getElapsedSinceBootMillis(); |
| }); |
| } |
| |
| private void processMessage(Message msg) { |
| switch (msg.what) { |
| case WifiMonitor.NETWORK_CONNECTION_EVENT: |
| // Trigger bugreport for successful connections that take abnormally long |
| if (mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled() |
| && mLastStartConnectTime > 0) { |
| long durationMs = mClock.getElapsedSinceBootMillis() - mLastStartConnectTime; |
| long abnormalConnectionDurationMs = |
| mDeviceConfigFacade.getAbnormalConnectionDurationMs(); |
| if (durationMs > abnormalConnectionDurationMs) { |
| final String bugTitle = "Wi-Fi Bugreport: Abnormal connection time"; |
| final String bugDetail = "Expected connection to take less than " |
| + abnormalConnectionDurationMs + " milliseconds. " |
| + "Actually took " + durationMs + " milliseconds."; |
| logv("Triggering bug report for abnormal connection time."); |
| mWifiThreadRunner.post(() -> |
| mClientModeImpl.takeBugReport(bugTitle, bugDetail)); |
| } |
| } |
| // Should reset last connection time after each connection regardless if bugreport |
| // is enabled or not. |
| mLastStartConnectTime = 0; |
| break; |
| default: |
| return; |
| } |
| } |
| |
| /** |
| * Refreshes recentAvailableNetworks with the latest available networks |
| * Adds new networks, removes old ones that have timed out. Should be called after Wifi |
| * framework decides what networks it is potentially connecting to. |
| * @param availableNetworks ScanDetail & Config list of potential connection |
| * candidates |
| */ |
| public void updateAvailableNetworks( |
| List<Pair<ScanDetail, WifiConfiguration>> availableNetworks) { |
| // Add new networks to mRecentAvailableNetworks |
| if (availableNetworks != null) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "updateAvailableNetworks: size = " + availableNetworks.size()); |
| } |
| for (Pair<ScanDetail, WifiConfiguration> pair : availableNetworks) { |
| final ScanDetail scanDetail = pair.first; |
| final WifiConfiguration config = pair.second; |
| ScanResult scanResult = scanDetail.getScanResult(); |
| if (scanResult == null) continue; |
| String bssid = scanResult.BSSID; |
| String ssid = "\"" + scanDetail.getSSID() + "\""; |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, " " + bssid + ": " + scanDetail.getSSID()); |
| } |
| // Cache the scanResult & WifiConfig |
| AvailableNetworkFailureCount availableNetworkFailureCount = |
| mRecentAvailableNetworks.get(bssid); |
| if (availableNetworkFailureCount == null) { |
| // New network is available |
| availableNetworkFailureCount = new AvailableNetworkFailureCount(config); |
| availableNetworkFailureCount.ssid = ssid; |
| |
| // Count AP for this SSID |
| Pair<AvailableNetworkFailureCount, Integer> ssidFailsAndApCount = |
| mSsidFailureCount.get(ssid); |
| if (ssidFailsAndApCount == null) { |
| // This is a new SSID, create new FailureCount for it and set AP count to 1 |
| ssidFailsAndApCount = Pair.create(new AvailableNetworkFailureCount(config), |
| 1); |
| // Do not re-enable Watchdog in LAST_TRIGGER_TIMEOUT_MILLIS |
| // after last time Watchdog be triggered |
| if (!mWatchdogAllowedToTrigger && (mTimeLastTrigger == 0 |
| || (mClock.getElapsedSinceBootMillis() - mTimeLastTrigger) |
| >= LAST_TRIGGER_TIMEOUT_MILLIS)) { |
| localLog("updateAvailableNetworks: setWatchdogTriggerEnabled to true"); |
| setWatchdogTriggerEnabled(true); |
| } |
| } else { |
| final Integer numberOfAps = ssidFailsAndApCount.second; |
| // This is not a new SSID, increment the AP count for it |
| ssidFailsAndApCount = Pair.create(ssidFailsAndApCount.first, |
| numberOfAps + 1); |
| } |
| mSsidFailureCount.put(ssid, ssidFailsAndApCount); |
| } |
| // refresh config if it is not null |
| if (config != null) { |
| availableNetworkFailureCount.config = config; |
| } |
| // If we saw a network, set its Age to -1 here, aging iteration will set it to 0 |
| availableNetworkFailureCount.age = -1; |
| mRecentAvailableNetworks.put(bssid, availableNetworkFailureCount); |
| } |
| } |
| |
| // Iterate through available networks updating timeout counts & removing networks. |
| Iterator<Map.Entry<String, AvailableNetworkFailureCount>> it = |
| mRecentAvailableNetworks.entrySet().iterator(); |
| while (it.hasNext()) { |
| Map.Entry<String, AvailableNetworkFailureCount> entry = it.next(); |
| if (entry.getValue().age < MAX_BSSID_AGE - 1) { |
| entry.getValue().age++; |
| } else { |
| // Decrement this SSID : AP count |
| String ssid = entry.getValue().ssid; |
| Pair<AvailableNetworkFailureCount, Integer> ssidFails = |
| mSsidFailureCount.get(ssid); |
| if (ssidFails != null) { |
| Integer apCount = ssidFails.second - 1; |
| if (apCount > 0) { |
| ssidFails = Pair.create(ssidFails.first, apCount); |
| mSsidFailureCount.put(ssid, ssidFails); |
| } else { |
| mSsidFailureCount.remove(ssid); |
| } |
| } else { |
| Log.d(TAG, "updateAvailableNetworks: SSID to AP count mismatch for " + ssid); |
| } |
| it.remove(); |
| } |
| } |
| if (mVerboseLoggingEnabled) Log.v(TAG, toString()); |
| } |
| |
| /** |
| * Increments the failure reason count for the given bssid. Performs a check to see if we have |
| * exceeded a failure threshold for all available networks, and executes the last resort restart |
| * @param bssid of the network that has failed connection, can be "any" |
| * @param reason Message id from ClientModeImpl for this failure |
| * @return true if watchdog triggers, returned for test visibility |
| */ |
| public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "noteConnectionFailureAndTriggerIfNeeded: [" + ssid + ", " + bssid + ", " |
| + reason + "]"); |
| } |
| |
| // Update failure count for the failing network |
| updateFailureCountForNetwork(ssid, bssid, reason); |
| |
| // If watchdog is not allowed to trigger it means a wifi restart is already triggered |
| if (!mWatchdogAllowedToTrigger) { |
| mWifiMetrics.incrementWatchdogTotalConnectionFailureCountAfterTrigger(); |
| mWatchdogFixedWifi = false; |
| } |
| // Have we met conditions to trigger the Watchdog Wifi restart? |
| boolean isRestartNeeded = checkTriggerCondition(); |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "isRestartNeeded = " + isRestartNeeded); |
| } |
| if (isRestartNeeded) { |
| if (getWifiWatchdogFeature()) { |
| // Stop the watchdog from triggering until re-enabled |
| localLog("Trigger recovery: setWatchdogTriggerEnabled to false"); |
| setWatchdogTriggerEnabled(false); |
| mWatchdogFixedWifi = true; |
| loge("Watchdog triggering recovery"); |
| mSsidLastTrigger = ssid; |
| mTimeLastTrigger = mClock.getElapsedSinceBootMillis(); |
| localLog(toString()); |
| mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG); |
| incrementWifiMetricsTriggerCounts(); |
| } else { |
| // auto bugreport if issue happens |
| loge("bugreport notification"); |
| setWatchdogTriggerEnabled(false); |
| takeBugReportWithCurrentProbability("Wifi Watchdog bite"); |
| } |
| } |
| return isRestartNeeded; |
| } |
| |
| /** |
| * Handles transitions entering and exiting ClientModeImpl ConnectedState |
| * Used to track wifistate, and perform watchdog count resetting |
| * @param isEntering true if called from ConnectedState.enter(), false for exit() |
| */ |
| public void connectedStateTransition(boolean isEntering) { |
| logv("connectedStateTransition: isEntering = " + isEntering); |
| |
| mWifiIsConnected = isEntering; |
| if (!isEntering) { |
| return; |
| } |
| if (!mWatchdogAllowedToTrigger && mWatchdogFixedWifi |
| && getWifiWatchdogFeature() |
| && checkIfAtleastOneNetworkHasEverConnected() |
| && checkIfConnectedBackToSameSsid() |
| && checkIfConnectedBssidHasEverFailed()) { |
| takeBugReportWithCurrentProbability("Wifi fixed after restart"); |
| // WiFi has connected after a Watchdog trigger, without any new networks becoming |
| // available, log a Watchdog success in wifi metrics |
| mWifiMetrics.incrementNumLastResortWatchdogSuccesses(); |
| long durationMs = mClock.getElapsedSinceBootMillis() - mTimeLastTrigger; |
| mWifiMetrics.setWatchdogSuccessTimeDurationMs(durationMs); |
| } |
| // If the watchdog trigger was disabled (it triggered), connecting means we did |
| // something right, re-enable it so it can fire again. |
| localLog("connectedStateTransition: setWatchdogTriggerEnabled to true"); |
| setWatchdogTriggerEnabled(true); |
| } |
| |
| /** |
| * Helper function to check if device connected to BSSID |
| * which is in BSSID failure list after watchdog trigger. |
| */ |
| private boolean checkIfConnectedBssidHasEverFailed() { |
| return mBssidFailureList.contains(mClientModeImpl.getWifiInfo().getBSSID()); |
| } |
| |
| /** |
| * Helper function to check if device connect back to same |
| * SSID after watchdog trigger |
| */ |
| private boolean checkIfConnectedBackToSameSsid() { |
| if (TextUtils.equals(mSsidLastTrigger, mClientModeImpl.getWifiInfo().getSSID())) { |
| return true; |
| } |
| localLog("checkIfConnectedBackToSameSsid: different SSID be connected"); |
| return false; |
| } |
| |
| /** |
| * Triggers a wifi specific bugreport with a based on the current trigger probability. |
| * @param bugDetail description of the bug |
| */ |
| private void takeBugReportWithCurrentProbability(String bugDetail) { |
| if (mBugReportProbability <= Math.random()) { |
| return; |
| } |
| (new Handler(mClientModeImplLooper)).post(() -> { |
| mClientModeImpl.takeBugReport(BUGREPORT_TITLE, bugDetail); |
| }); |
| } |
| |
| /** |
| * Increments the failure reason count for the given network, in 'mSsidFailureCount' |
| * Failures are counted per SSID, either; by using the ssid string when the bssid is "any" |
| * or by looking up the ssid attached to a specific bssid |
| * An unused set of counts is also kept which is bssid specific, in 'mRecentAvailableNetworks' |
| * @param ssid of the network that has failed connection |
| * @param bssid of the network that has failed connection, can be "any" |
| * @param reason Message id from ClientModeImpl for this failure |
| */ |
| private void updateFailureCountForNetwork(String ssid, String bssid, int reason) { |
| logv("updateFailureCountForNetwork: [" + ssid + ", " + bssid + ", " |
| + reason + "]"); |
| if (BSSID_ANY.equals(bssid)) { |
| incrementSsidFailureCount(ssid, reason); |
| } else { |
| // Bssid count is actually unused except for logging purposes |
| // SSID count is incremented within the BSSID counting method |
| incrementBssidFailureCount(ssid, bssid, reason); |
| mBssidFailureList.add(bssid); |
| } |
| } |
| |
| /** |
| * Update the per-SSID failure count |
| * @param ssid the ssid to increment failure count for |
| * @param reason the failure type to increment count for |
| */ |
| private void incrementSsidFailureCount(String ssid, int reason) { |
| Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid); |
| if (ssidFails == null) { |
| Log.d(TAG, "updateFailureCountForNetwork: No networks for ssid = " + ssid); |
| return; |
| } |
| AvailableNetworkFailureCount failureCount = ssidFails.first; |
| failureCount.incrementFailureCount(reason); |
| } |
| |
| /** |
| * Update the per-BSSID failure count |
| * @param bssid the bssid to increment failure count for |
| * @param reason the failure type to increment count for |
| */ |
| private void incrementBssidFailureCount(String ssid, String bssid, int reason) { |
| AvailableNetworkFailureCount availableNetworkFailureCount = |
| mRecentAvailableNetworks.get(bssid); |
| if (availableNetworkFailureCount == null) { |
| Log.d(TAG, "updateFailureCountForNetwork: Unable to find Network [" + ssid |
| + ", " + bssid + "]"); |
| return; |
| } |
| if (!availableNetworkFailureCount.ssid.equals(ssid)) { |
| Log.d(TAG, "updateFailureCountForNetwork: Failed connection attempt has" |
| + " wrong ssid. Failed [" + ssid + ", " + bssid + "], buffered [" |
| + availableNetworkFailureCount.ssid + ", " + bssid + "]"); |
| return; |
| } |
| if (availableNetworkFailureCount.config == null) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "updateFailureCountForNetwork: network has no config [" |
| + ssid + ", " + bssid + "]"); |
| } |
| } |
| availableNetworkFailureCount.incrementFailureCount(reason); |
| incrementSsidFailureCount(ssid, reason); |
| } |
| |
| /** |
| * Helper function to check if we should ignore BSSID update. |
| * @param bssid BSSID of the access point |
| * @return true if we should ignore BSSID update |
| */ |
| public boolean shouldIgnoreBssidUpdate(String bssid) { |
| return mWatchdogAllowedToTrigger |
| && isBssidOnlyApOfSsid(bssid) |
| && isSingleSsidRecorded() |
| && checkIfAtleastOneNetworkHasEverConnected(); |
| } |
| |
| /** |
| * Helper function to check if we should ignore SSID update. |
| * @return true if should ignore SSID update |
| */ |
| public boolean shouldIgnoreSsidUpdate() { |
| return mWatchdogAllowedToTrigger |
| && isSingleSsidRecorded() |
| && checkIfAtleastOneNetworkHasEverConnected(); |
| } |
| |
| /** |
| * Check the specified BSSID is the only BSSID for its corresponding SSID. |
| * @param bssid BSSID of the access point |
| * @return true if only BSSID for its corresponding SSID be observed |
| */ |
| private boolean isBssidOnlyApOfSsid(String bssid) { |
| AvailableNetworkFailureCount availableNetworkFailureCount = |
| mRecentAvailableNetworks.get(bssid); |
| if (availableNetworkFailureCount == null) { |
| return false; |
| } |
| String ssid = availableNetworkFailureCount.ssid; |
| Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid); |
| if (ssidFails == null) { |
| Log.d(TAG, "isOnlyBssidAvailable: Could not find SSID count for " + ssid); |
| return false; |
| } |
| if (ssidFails.second != 1) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Check there is only single SSID be observed. |
| * @return true if only single SSID be observed. |
| */ |
| private boolean isSingleSsidRecorded() { |
| return (mSsidFailureCount.size() == 1); |
| } |
| |
| /** |
| * Check trigger condition: For all available networks, have we met a failure threshold for each |
| * of them, and have previously connected to at-least one of the available networks |
| * @return is the trigger condition true |
| */ |
| private boolean checkTriggerCondition() { |
| if (mVerboseLoggingEnabled) Log.v(TAG, "checkTriggerCondition."); |
| // Don't check Watchdog trigger if wifi is in a connected state |
| // (This should not occur, but we want to protect against any race conditions) |
| if (mWifiIsConnected) return false; |
| // Don't check Watchdog trigger if trigger is not enabled |
| if (!mWatchdogAllowedToTrigger) return false; |
| |
| for (Map.Entry<String, AvailableNetworkFailureCount> entry |
| : mRecentAvailableNetworks.entrySet()) { |
| if (!isOverFailureThreshold(entry.getKey())) { |
| // This available network is not over failure threshold, meaning we still have a |
| // network to try connecting to |
| return false; |
| } |
| } |
| // We have met the failure count for every available network. |
| // Trigger restart if there exists at-least one network that we have previously connected. |
| boolean atleastOneNetworkHasEverConnected = checkIfAtleastOneNetworkHasEverConnected(); |
| logv("checkTriggerCondition: return = " + atleastOneNetworkHasEverConnected); |
| return checkIfAtleastOneNetworkHasEverConnected(); |
| } |
| |
| private boolean checkIfAtleastOneNetworkHasEverConnected() { |
| for (Map.Entry<String, AvailableNetworkFailureCount> entry |
| : mRecentAvailableNetworks.entrySet()) { |
| if (entry.getValue().config != null |
| && entry.getValue().config.getNetworkSelectionStatus().hasEverConnected()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Update WifiMetrics with various Watchdog stats (trigger counts, failed network counts) |
| */ |
| private void incrementWifiMetricsTriggerCounts() { |
| if (mVerboseLoggingEnabled) Log.v(TAG, "incrementWifiMetricsTriggerCounts."); |
| mWifiMetrics.incrementNumLastResortWatchdogTriggers(); |
| mWifiMetrics.addCountToNumLastResortWatchdogAvailableNetworksTotal( |
| mSsidFailureCount.size()); |
| // Number of networks over each failure type threshold, present at trigger time |
| int badAuth = 0; |
| int badAssoc = 0; |
| int badDhcp = 0; |
| int badSum = 0; |
| for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry |
| : mSsidFailureCount.entrySet()) { |
| badSum = entry.getValue().first.associationRejection |
| + entry.getValue().first.authenticationFailure |
| + entry.getValue().first.dhcpFailure; |
| // count as contributor if over half of badSum. |
| if (badSum >= FAILURE_THRESHOLD) { |
| badAssoc += (entry.getValue().first.associationRejection >= badSum / 2) ? 1 : 0; |
| badAuth += (entry.getValue().first.authenticationFailure >= badSum / 2) ? 1 : 0; |
| badDhcp += (entry.getValue().first.dhcpFailure >= badSum / 2) ? 1 : 0; |
| } |
| } |
| if (badAuth > 0) { |
| mWifiMetrics.addCountToNumLastResortWatchdogBadAuthenticationNetworksTotal(badAuth); |
| mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadAuthentication(); |
| } |
| if (badAssoc > 0) { |
| mWifiMetrics.addCountToNumLastResortWatchdogBadAssociationNetworksTotal(badAssoc); |
| mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadAssociation(); |
| } |
| if (badDhcp > 0) { |
| mWifiMetrics.addCountToNumLastResortWatchdogBadDhcpNetworksTotal(badDhcp); |
| mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadDhcp(); |
| } |
| } |
| |
| /** |
| * Clear all failure counts |
| */ |
| public void clearAllFailureCounts() { |
| if (mVerboseLoggingEnabled) Log.v(TAG, "clearAllFailureCounts."); |
| for (Map.Entry<String, AvailableNetworkFailureCount> entry |
| : mRecentAvailableNetworks.entrySet()) { |
| final AvailableNetworkFailureCount failureCount = entry.getValue(); |
| failureCount.resetCounts(); |
| } |
| for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry |
| : mSsidFailureCount.entrySet()) { |
| final AvailableNetworkFailureCount failureCount = entry.getValue().first; |
| failureCount.resetCounts(); |
| } |
| mBssidFailureList.clear(); |
| } |
| /** |
| * Gets the buffer of recently available networks |
| */ |
| Map<String, AvailableNetworkFailureCount> getRecentAvailableNetworks() { |
| return mRecentAvailableNetworks; |
| } |
| |
| /** |
| * Activates or deactivates the Watchdog trigger. Counting and network buffering still occurs |
| * @param enable true to enable the Watchdog trigger, false to disable it |
| */ |
| private void setWatchdogTriggerEnabled(boolean enable) { |
| if (mVerboseLoggingEnabled) Log.v(TAG, "setWatchdogTriggerEnabled: enable = " + enable); |
| // Reset failure counts before actives watchdog |
| if (enable) { |
| clearAllFailureCounts(); |
| } |
| mWatchdogAllowedToTrigger = enable; |
| } |
| |
| /** |
| * Prints all networks & counts within mRecentAvailableNetworks to string |
| */ |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("mWatchdogFeatureEnabled: ").append(getWifiWatchdogFeature()); |
| sb.append("\nmWatchdogAllowedToTrigger: ").append(mWatchdogAllowedToTrigger); |
| sb.append("\nmWifiIsConnected: ").append(mWifiIsConnected); |
| sb.append("\nmRecentAvailableNetworks: ").append(mRecentAvailableNetworks.size()); |
| for (Map.Entry<String, AvailableNetworkFailureCount> entry |
| : mRecentAvailableNetworks.entrySet()) { |
| sb.append("\n ").append(entry.getKey()).append(": ").append(entry.getValue()) |
| .append(", Age: ").append(entry.getValue().age); |
| } |
| sb.append("\nmSsidFailureCount:"); |
| for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry : |
| mSsidFailureCount.entrySet()) { |
| final AvailableNetworkFailureCount failureCount = entry.getValue().first; |
| final Integer apCount = entry.getValue().second; |
| sb.append("\n").append(entry.getKey()).append(": ").append(apCount).append(",") |
| .append(failureCount.toString()); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * @param bssid bssid to check the failures for |
| * @return true if sum of failure count is over FAILURE_THRESHOLD |
| */ |
| public boolean isOverFailureThreshold(String bssid) { |
| return (getFailureCount(bssid, FAILURE_CODE_ASSOCIATION) |
| + getFailureCount(bssid, FAILURE_CODE_AUTHENTICATION) |
| + getFailureCount(bssid, FAILURE_CODE_DHCP)) >= FAILURE_THRESHOLD; |
| } |
| |
| /** |
| * Get the failure count for a specific bssid. This actually checks the ssid attached to the |
| * BSSID and returns the SSID count |
| * @param reason failure reason to get count for |
| */ |
| public int getFailureCount(String bssid, int reason) { |
| AvailableNetworkFailureCount availableNetworkFailureCount = |
| mRecentAvailableNetworks.get(bssid); |
| if (availableNetworkFailureCount == null) { |
| return 0; |
| } |
| String ssid = availableNetworkFailureCount.ssid; |
| Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid); |
| if (ssidFails == null) { |
| Log.d(TAG, "getFailureCount: Could not find SSID count for " + ssid); |
| return 0; |
| } |
| final AvailableNetworkFailureCount failCount = ssidFails.first; |
| switch (reason) { |
| case FAILURE_CODE_ASSOCIATION: |
| return failCount.associationRejection; |
| case FAILURE_CODE_AUTHENTICATION: |
| return failCount.authenticationFailure; |
| case FAILURE_CODE_DHCP: |
| return failCount.dhcpFailure; |
| default: |
| return 0; |
| } |
| } |
| |
| /** |
| * Sets whether wifi watchdog should trigger recovery |
| */ |
| public void setWifiWatchdogFeature(boolean enable) { |
| logv("setWifiWatchdogFeature: " + enable); |
| mWatchdogFeatureEnabled = enable; |
| // for debugging purpose, reset mWatchdogAllowedToTrigger as well |
| setWatchdogTriggerEnabled(true); |
| } |
| |
| /** |
| * Returns whether wifi watchdog should trigger recovery. |
| */ |
| public boolean getWifiWatchdogFeature() { |
| if (mWatchdogFeatureEnabled == null) { |
| mWatchdogFeatureEnabled = mContext.getResources().getBoolean( |
| R.bool.config_wifi_watchdog_enabled); |
| } |
| return mWatchdogFeatureEnabled; |
| } |
| |
| protected void enableVerboseLogging(int verbose) { |
| if (verbose > 0) { |
| mVerboseLoggingEnabled = true; |
| } else { |
| mVerboseLoggingEnabled = false; |
| } |
| } |
| |
| @VisibleForTesting |
| protected void setBugReportProbability(double newProbability) { |
| mBugReportProbability = newProbability; |
| } |
| |
| /** |
| * This class holds the failure counts for an 'available network' (one of the potential |
| * candidates for connection, as determined by framework). |
| */ |
| public static class AvailableNetworkFailureCount { |
| /** |
| * WifiConfiguration associated with this network. Can be null for Ephemeral networks |
| */ |
| public WifiConfiguration config; |
| /** |
| * SSID of the network (from ScanDetail) |
| */ |
| public String ssid = ""; |
| /** |
| * Number of times network has failed due to Association Rejection |
| */ |
| public int associationRejection = 0; |
| /** |
| * Number of times network has failed due to Authentication Failure or SSID_TEMP_DISABLED |
| */ |
| public int authenticationFailure = 0; |
| /** |
| * Number of times network has failed due to DHCP failure |
| */ |
| public int dhcpFailure = 0; |
| /** |
| * Number of scanResults since this network was last seen |
| */ |
| public int age = 0; |
| |
| AvailableNetworkFailureCount(WifiConfiguration configParam) { |
| this.config = configParam; |
| } |
| |
| /** |
| * @param reason failure reason to increment count for |
| */ |
| public void incrementFailureCount(int reason) { |
| switch (reason) { |
| case FAILURE_CODE_ASSOCIATION: |
| associationRejection++; |
| break; |
| case FAILURE_CODE_AUTHENTICATION: |
| authenticationFailure++; |
| break; |
| case FAILURE_CODE_DHCP: |
| dhcpFailure++; |
| break; |
| default: //do nothing |
| } |
| } |
| |
| /** |
| * Set all failure counts for this network to 0 |
| */ |
| void resetCounts() { |
| associationRejection = 0; |
| authenticationFailure = 0; |
| dhcpFailure = 0; |
| } |
| |
| public String toString() { |
| return ssid + " HasEverConnected: " + ((config != null) |
| ? config.getNetworkSelectionStatus().hasEverConnected() : "null_config") |
| + ", Failures: {" |
| + "Assoc: " + associationRejection |
| + ", Auth: " + authenticationFailure |
| + ", Dhcp: " + dhcpFailure |
| + "}"; |
| } |
| } |
| |
| /** |
| * Helper function for logging into local log buffer. |
| */ |
| private void localLog(String s) { |
| mLocalLog.log(s); |
| } |
| |
| private void logv(String s) { |
| mLocalLog.log(s); |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, s); |
| } |
| } |
| |
| private void loge(String s) { |
| mLocalLog.log(s); |
| Log.e(TAG, s); |
| } |
| |
| /** |
| * Dump the local log buffer and other internal state of WifiLastResortWatchdog. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of WifiLastResortWatchdog"); |
| pw.println("WifiLastResortWatchdog - Log Begin ----"); |
| mLocalLog.dump(fd, pw, args); |
| pw.println("WifiLastResortWatchdog - Log End ----"); |
| } |
| } |