Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2016 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 android.net.wifi.ScanResult;
     20 import android.net.wifi.WifiConfiguration;
     21 import android.os.Handler;
     22 import android.os.Looper;
     23 import android.util.Log;
     24 import android.util.Pair;
     25 
     26 import com.android.internal.annotations.VisibleForTesting;
     27 
     28 import java.util.HashMap;
     29 import java.util.Iterator;
     30 import java.util.List;
     31 import java.util.Map;
     32 
     33 /**
     34  * This Class is a Work-In-Progress, intended behavior is as follows:
     35  * Essentially this class automates a user toggling 'Airplane Mode' when WiFi "won't work".
     36  * IF each available saved network has failed connecting more times than the FAILURE_THRESHOLD
     37  * THEN Watchdog will restart Supplicant, wifi driver and return WifiStateMachine to InitialState.
     38  */
     39 public class WifiLastResortWatchdog {
     40     private static final String TAG = "WifiLastResortWatchdog";
     41     private boolean mVerboseLoggingEnabled = false;
     42     /**
     43      * Association Failure code
     44      */
     45     public static final int FAILURE_CODE_ASSOCIATION = 1;
     46     /**
     47      * Authentication Failure code
     48      */
     49     public static final int FAILURE_CODE_AUTHENTICATION = 2;
     50     /**
     51      * Dhcp Failure code
     52      */
     53     public static final int FAILURE_CODE_DHCP = 3;
     54     /**
     55      * Maximum number of scan results received since we last saw a BSSID.
     56      * If it is not seen before this limit is reached, the network is culled
     57      */
     58     public static final int MAX_BSSID_AGE = 10;
     59     /**
     60      * BSSID used to increment failure counts against ALL bssids associated with a particular SSID
     61      */
     62     public static final String BSSID_ANY = "any";
     63     /**
     64      * Failure count that each available networks must meet to possibly trigger the Watchdog
     65      */
     66     public static final int FAILURE_THRESHOLD = 7;
     67     public static final String BUGREPORT_TITLE = "Wifi watchdog triggered";
     68     public static final double PROB_TAKE_BUGREPORT_DEFAULT = 0.08;
     69 
     70     /**
     71      * Cached WifiConfigurations of available networks seen within MAX_BSSID_AGE scan results
     72      * Key:BSSID, Value:Counters of failure types
     73      */
     74     private Map<String, AvailableNetworkFailureCount> mRecentAvailableNetworks = new HashMap<>();
     75 
     76     /**
     77      * Map of SSID to <FailureCount, AP count>, used to count failures & number of access points
     78      * belonging to an SSID.
     79      */
     80     private Map<String, Pair<AvailableNetworkFailureCount, Integer>> mSsidFailureCount =
     81             new HashMap<>();
     82 
     83     // Tracks: if WifiStateMachine is in ConnectedState
     84     private boolean mWifiIsConnected = false;
     85     // Is Watchdog allowed to trigger now? Set to false after triggering. Set to true after
     86     // successfully connecting or a new network (SSID) becomes available to connect to.
     87     private boolean mWatchdogAllowedToTrigger = true;
     88     private long mTimeLastTrigger;
     89 
     90     private SelfRecovery mSelfRecovery;
     91     private WifiMetrics mWifiMetrics;
     92     private WifiStateMachine mWifiStateMachine;
     93     private Looper mWifiStateMachineLooper;
     94     private double mBugReportProbability = PROB_TAKE_BUGREPORT_DEFAULT;
     95     private Clock mClock;
     96     // If any connection failure happened after watchdog triggering restart then assume watchdog
     97     // did not fix the problem
     98     private boolean mWatchdogFixedWifi = true;
     99 
    100     WifiLastResortWatchdog(SelfRecovery selfRecovery, Clock clock, WifiMetrics wifiMetrics,
    101             WifiStateMachine wsm, Looper wifiStateMachineLooper) {
    102         mSelfRecovery = selfRecovery;
    103         mClock = clock;
    104         mWifiMetrics = wifiMetrics;
    105         mWifiStateMachine = wsm;
    106         mWifiStateMachineLooper = wifiStateMachineLooper;
    107     }
    108 
    109     /**
    110      * Refreshes recentAvailableNetworks with the latest available networks
    111      * Adds new networks, removes old ones that have timed out. Should be called after Wifi
    112      * framework decides what networks it is potentially connecting to.
    113      * @param availableNetworks ScanDetail & Config list of potential connection
    114      * candidates
    115      */
    116     public void updateAvailableNetworks(
    117             List<Pair<ScanDetail, WifiConfiguration>> availableNetworks) {
    118         if (mVerboseLoggingEnabled) {
    119             Log.v(TAG, "updateAvailableNetworks: size = " + availableNetworks.size());
    120         }
    121         // Add new networks to mRecentAvailableNetworks
    122         if (availableNetworks != null) {
    123             for (Pair<ScanDetail, WifiConfiguration> pair : availableNetworks) {
    124                 final ScanDetail scanDetail = pair.first;
    125                 final WifiConfiguration config = pair.second;
    126                 ScanResult scanResult = scanDetail.getScanResult();
    127                 if (scanResult == null) continue;
    128                 String bssid = scanResult.BSSID;
    129                 String ssid = "\"" + scanDetail.getSSID() + "\"";
    130                 if (mVerboseLoggingEnabled) {
    131                     Log.v(TAG, " " + bssid + ": " + scanDetail.getSSID());
    132                 }
    133                 // Cache the scanResult & WifiConfig
    134                 AvailableNetworkFailureCount availableNetworkFailureCount =
    135                         mRecentAvailableNetworks.get(bssid);
    136                 if (availableNetworkFailureCount == null) {
    137                     // New network is available
    138                     availableNetworkFailureCount = new AvailableNetworkFailureCount(config);
    139                     availableNetworkFailureCount.ssid = ssid;
    140 
    141                     // Count AP for this SSID
    142                     Pair<AvailableNetworkFailureCount, Integer> ssidFailsAndApCount =
    143                             mSsidFailureCount.get(ssid);
    144                     if (ssidFailsAndApCount == null) {
    145                         // This is a new SSID, create new FailureCount for it and set AP count to 1
    146                         ssidFailsAndApCount = Pair.create(new AvailableNetworkFailureCount(config),
    147                                 1);
    148                         setWatchdogTriggerEnabled(true);
    149                     } else {
    150                         final Integer numberOfAps = ssidFailsAndApCount.second;
    151                         // This is not a new SSID, increment the AP count for it
    152                         ssidFailsAndApCount = Pair.create(ssidFailsAndApCount.first,
    153                                 numberOfAps + 1);
    154                     }
    155                     mSsidFailureCount.put(ssid, ssidFailsAndApCount);
    156                 }
    157                 // refresh config if it is not null
    158                 if (config != null) {
    159                     availableNetworkFailureCount.config = config;
    160                 }
    161                 // If we saw a network, set its Age to -1 here, aging iteration will set it to 0
    162                 availableNetworkFailureCount.age = -1;
    163                 mRecentAvailableNetworks.put(bssid, availableNetworkFailureCount);
    164             }
    165         }
    166 
    167         // Iterate through available networks updating timeout counts & removing networks.
    168         Iterator<Map.Entry<String, AvailableNetworkFailureCount>> it =
    169                 mRecentAvailableNetworks.entrySet().iterator();
    170         while (it.hasNext()) {
    171             Map.Entry<String, AvailableNetworkFailureCount> entry = it.next();
    172             if (entry.getValue().age < MAX_BSSID_AGE - 1) {
    173                 entry.getValue().age++;
    174             } else {
    175                 // Decrement this SSID : AP count
    176                 String ssid = entry.getValue().ssid;
    177                 Pair<AvailableNetworkFailureCount, Integer> ssidFails =
    178                             mSsidFailureCount.get(ssid);
    179                 if (ssidFails != null) {
    180                     Integer apCount = ssidFails.second - 1;
    181                     if (apCount > 0) {
    182                         ssidFails = Pair.create(ssidFails.first, apCount);
    183                         mSsidFailureCount.put(ssid, ssidFails);
    184                     } else {
    185                         mSsidFailureCount.remove(ssid);
    186                     }
    187                 } else {
    188                     Log.d(TAG, "updateAvailableNetworks: SSID to AP count mismatch for " + ssid);
    189                 }
    190                 it.remove();
    191             }
    192         }
    193         if (mVerboseLoggingEnabled) Log.v(TAG, toString());
    194     }
    195 
    196     /**
    197      * Increments the failure reason count for the given bssid. Performs a check to see if we have
    198      * exceeded a failure threshold for all available networks, and executes the last resort restart
    199      * @param bssid of the network that has failed connection, can be "any"
    200      * @param reason Message id from WifiStateMachine for this failure
    201      * @return true if watchdog triggers, returned for test visibility
    202      */
    203     public boolean noteConnectionFailureAndTriggerIfNeeded(String ssid, String bssid, int reason) {
    204         if (mVerboseLoggingEnabled) {
    205             Log.v(TAG, "noteConnectionFailureAndTriggerIfNeeded: [" + ssid + ", " + bssid + ", "
    206                     + reason + "]");
    207         }
    208 
    209         // Update failure count for the failing network
    210         updateFailureCountForNetwork(ssid, bssid, reason);
    211 
    212         // If watchdog is not allowed to trigger it means a wifi restart is already triggered
    213         if (!mWatchdogAllowedToTrigger) {
    214             mWifiMetrics.incrementWatchdogTotalConnectionFailureCountAfterTrigger();
    215             mWatchdogFixedWifi = false;
    216         }
    217         // Have we met conditions to trigger the Watchdog Wifi restart?
    218         boolean isRestartNeeded = checkTriggerCondition();
    219         if (mVerboseLoggingEnabled) {
    220             Log.v(TAG, "isRestartNeeded = " + isRestartNeeded);
    221         }
    222         if (isRestartNeeded) {
    223             // Stop the watchdog from triggering until re-enabled
    224             setWatchdogTriggerEnabled(false);
    225             mWatchdogFixedWifi = true;
    226             Log.e(TAG, "Watchdog triggering recovery");
    227             mTimeLastTrigger = mClock.getElapsedSinceBootMillis();
    228             mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
    229             // increment various watchdog trigger count stats
    230             incrementWifiMetricsTriggerCounts();
    231             clearAllFailureCounts();
    232         }
    233         return isRestartNeeded;
    234     }
    235 
    236     /**
    237      * Handles transitions entering and exiting WifiStateMachine ConnectedState
    238      * Used to track wifistate, and perform watchdog count resetting
    239      * @param isEntering true if called from ConnectedState.enter(), false for exit()
    240      */
    241     public void connectedStateTransition(boolean isEntering) {
    242         if (mVerboseLoggingEnabled) {
    243             Log.v(TAG, "connectedStateTransition: isEntering = " + isEntering);
    244         }
    245         mWifiIsConnected = isEntering;
    246         if (!isEntering) {
    247             return;
    248         }
    249         if (!mWatchdogAllowedToTrigger && mWatchdogFixedWifi
    250                 && checkIfAtleastOneNetworkHasEverConnected()) {
    251             takeBugReportWithCurrentProbability("Wifi fixed after restart");
    252             // WiFi has connected after a Watchdog trigger, without any new networks becoming
    253             // available, log a Watchdog success in wifi metrics
    254             mWifiMetrics.incrementNumLastResortWatchdogSuccesses();
    255             long durationMs = mClock.getElapsedSinceBootMillis() - mTimeLastTrigger;
    256             mWifiMetrics.setWatchdogSuccessTimeDurationMs(durationMs);
    257         }
    258         // We connected to something! Reset failure counts for everything
    259         clearAllFailureCounts();
    260         // If the watchdog trigger was disabled (it triggered), connecting means we did
    261         // something right, re-enable it so it can fire again.
    262         setWatchdogTriggerEnabled(true);
    263     }
    264 
    265     /**
    266      * Triggers a wifi specific bugreport with a based on the current trigger probability.
    267      * @param bugDetail description of the bug
    268      */
    269     private void takeBugReportWithCurrentProbability(String bugDetail) {
    270         if (mBugReportProbability <= Math.random()) {
    271             return;
    272         }
    273         (new Handler(mWifiStateMachineLooper)).post(() -> {
    274             mWifiStateMachine.takeBugReport(BUGREPORT_TITLE, bugDetail);
    275         });
    276     }
    277 
    278     /**
    279      * Increments the failure reason count for the given network, in 'mSsidFailureCount'
    280      * Failures are counted per SSID, either; by using the ssid string when the bssid is "any"
    281      * or by looking up the ssid attached to a specific bssid
    282      * An unused set of counts is also kept which is bssid specific, in 'mRecentAvailableNetworks'
    283      * @param ssid of the network that has failed connection
    284      * @param bssid of the network that has failed connection, can be "any"
    285      * @param reason Message id from WifiStateMachine for this failure
    286      */
    287     private void updateFailureCountForNetwork(String ssid, String bssid, int reason) {
    288         if (mVerboseLoggingEnabled) {
    289             Log.v(TAG, "updateFailureCountForNetwork: [" + ssid + ", " + bssid + ", "
    290                     + reason + "]");
    291         }
    292         if (BSSID_ANY.equals(bssid)) {
    293             incrementSsidFailureCount(ssid, reason);
    294         } else {
    295             // Bssid count is actually unused except for logging purposes
    296             // SSID count is incremented within the BSSID counting method
    297             incrementBssidFailureCount(ssid, bssid, reason);
    298         }
    299     }
    300 
    301     /**
    302      * Update the per-SSID failure count
    303      * @param ssid the ssid to increment failure count for
    304      * @param reason the failure type to increment count for
    305      */
    306     private void incrementSsidFailureCount(String ssid, int reason) {
    307         Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid);
    308         if (ssidFails == null) {
    309             Log.d(TAG, "updateFailureCountForNetwork: No networks for ssid = " + ssid);
    310             return;
    311         }
    312         AvailableNetworkFailureCount failureCount = ssidFails.first;
    313         failureCount.incrementFailureCount(reason);
    314     }
    315 
    316     /**
    317      * Update the per-BSSID failure count
    318      * @param bssid the bssid to increment failure count for
    319      * @param reason the failure type to increment count for
    320      */
    321     private void incrementBssidFailureCount(String ssid, String bssid, int reason) {
    322         AvailableNetworkFailureCount availableNetworkFailureCount =
    323                 mRecentAvailableNetworks.get(bssid);
    324         if (availableNetworkFailureCount == null) {
    325             Log.d(TAG, "updateFailureCountForNetwork: Unable to find Network [" + ssid
    326                     + ", " + bssid + "]");
    327             return;
    328         }
    329         if (!availableNetworkFailureCount.ssid.equals(ssid)) {
    330             Log.d(TAG, "updateFailureCountForNetwork: Failed connection attempt has"
    331                     + " wrong ssid. Failed [" + ssid + ", " + bssid + "], buffered ["
    332                     + availableNetworkFailureCount.ssid + ", " + bssid + "]");
    333             return;
    334         }
    335         if (availableNetworkFailureCount.config == null) {
    336             if (mVerboseLoggingEnabled) {
    337                 Log.v(TAG, "updateFailureCountForNetwork: network has no config ["
    338                         + ssid + ", " + bssid + "]");
    339             }
    340         }
    341         availableNetworkFailureCount.incrementFailureCount(reason);
    342         incrementSsidFailureCount(ssid, reason);
    343     }
    344 
    345     /**
    346      * Check trigger condition: For all available networks, have we met a failure threshold for each
    347      * of them, and have previously connected to at-least one of the available networks
    348      * @return is the trigger condition true
    349      */
    350     private boolean checkTriggerCondition() {
    351         if (mVerboseLoggingEnabled) Log.v(TAG, "checkTriggerCondition.");
    352         // Don't check Watchdog trigger if wifi is in a connected state
    353         // (This should not occur, but we want to protect against any race conditions)
    354         if (mWifiIsConnected) return false;
    355         // Don't check Watchdog trigger if trigger is not enabled
    356         if (!mWatchdogAllowedToTrigger) return false;
    357 
    358         for (Map.Entry<String, AvailableNetworkFailureCount> entry
    359                 : mRecentAvailableNetworks.entrySet()) {
    360             if (!isOverFailureThreshold(entry.getKey())) {
    361                 // This available network is not over failure threshold, meaning we still have a
    362                 // network to try connecting to
    363                 return false;
    364             }
    365         }
    366         // We have met the failure count for every available network.
    367         // Trigger restart if there exists at-least one network that we have previously connected.
    368         boolean atleastOneNetworkHasEverConnected = checkIfAtleastOneNetworkHasEverConnected();
    369         if (mVerboseLoggingEnabled) {
    370             Log.v(TAG, "checkTriggerCondition: return = " + atleastOneNetworkHasEverConnected);
    371         }
    372         return checkIfAtleastOneNetworkHasEverConnected();
    373     }
    374 
    375     private boolean checkIfAtleastOneNetworkHasEverConnected() {
    376         for (Map.Entry<String, AvailableNetworkFailureCount> entry
    377                 : mRecentAvailableNetworks.entrySet()) {
    378             if (entry.getValue().config != null
    379                     && entry.getValue().config.getNetworkSelectionStatus().getHasEverConnected()) {
    380                 return true;
    381             }
    382         }
    383         return false;
    384     }
    385 
    386     /**
    387      * Update WifiMetrics with various Watchdog stats (trigger counts, failed network counts)
    388      */
    389     private void incrementWifiMetricsTriggerCounts() {
    390         if (mVerboseLoggingEnabled) Log.v(TAG, "incrementWifiMetricsTriggerCounts.");
    391         mWifiMetrics.incrementNumLastResortWatchdogTriggers();
    392         mWifiMetrics.addCountToNumLastResortWatchdogAvailableNetworksTotal(
    393                 mSsidFailureCount.size());
    394         // Number of networks over each failure type threshold, present at trigger time
    395         int badAuth = 0;
    396         int badAssoc = 0;
    397         int badDhcp = 0;
    398         for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry
    399                 : mSsidFailureCount.entrySet()) {
    400             badAuth += (entry.getValue().first.authenticationFailure >= FAILURE_THRESHOLD) ? 1 : 0;
    401             badAssoc += (entry.getValue().first.associationRejection >= FAILURE_THRESHOLD) ? 1 : 0;
    402             badDhcp += (entry.getValue().first.dhcpFailure >= FAILURE_THRESHOLD) ? 1 : 0;
    403         }
    404         if (badAuth > 0) {
    405             mWifiMetrics.addCountToNumLastResortWatchdogBadAuthenticationNetworksTotal(badAuth);
    406             mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadAuthentication();
    407         }
    408         if (badAssoc > 0) {
    409             mWifiMetrics.addCountToNumLastResortWatchdogBadAssociationNetworksTotal(badAssoc);
    410             mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadAssociation();
    411         }
    412         if (badDhcp > 0) {
    413             mWifiMetrics.addCountToNumLastResortWatchdogBadDhcpNetworksTotal(badDhcp);
    414             mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadDhcp();
    415         }
    416     }
    417 
    418     /**
    419      * Clear failure counts for each network in recentAvailableNetworks
    420      */
    421     public void clearAllFailureCounts() {
    422         if (mVerboseLoggingEnabled) Log.v(TAG, "clearAllFailureCounts.");
    423         for (Map.Entry<String, AvailableNetworkFailureCount> entry
    424                 : mRecentAvailableNetworks.entrySet()) {
    425             final AvailableNetworkFailureCount failureCount = entry.getValue();
    426             failureCount.resetCounts();
    427         }
    428         for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry
    429                 : mSsidFailureCount.entrySet()) {
    430             final AvailableNetworkFailureCount failureCount = entry.getValue().first;
    431             failureCount.resetCounts();
    432         }
    433     }
    434     /**
    435      * Gets the buffer of recently available networks
    436      */
    437     Map<String, AvailableNetworkFailureCount> getRecentAvailableNetworks() {
    438         return mRecentAvailableNetworks;
    439     }
    440 
    441     /**
    442      * Activates or deactivates the Watchdog trigger. Counting and network buffering still occurs
    443      * @param enable true to enable the Watchdog trigger, false to disable it
    444      */
    445     private void setWatchdogTriggerEnabled(boolean enable) {
    446         if (mVerboseLoggingEnabled) Log.v(TAG, "setWatchdogTriggerEnabled: enable = " + enable);
    447         mWatchdogAllowedToTrigger = enable;
    448     }
    449 
    450     /**
    451      * Prints all networks & counts within mRecentAvailableNetworks to string
    452      */
    453     public String toString() {
    454         StringBuilder sb = new StringBuilder();
    455         sb.append("mWatchdogAllowedToTrigger: ").append(mWatchdogAllowedToTrigger);
    456         sb.append("\nmWifiIsConnected: ").append(mWifiIsConnected);
    457         sb.append("\nmRecentAvailableNetworks: ").append(mRecentAvailableNetworks.size());
    458         for (Map.Entry<String, AvailableNetworkFailureCount> entry
    459                 : mRecentAvailableNetworks.entrySet()) {
    460             sb.append("\n ").append(entry.getKey()).append(": ").append(entry.getValue())
    461                 .append(", Age: ").append(entry.getValue().age);
    462         }
    463         sb.append("\nmSsidFailureCount:");
    464         for (Map.Entry<String, Pair<AvailableNetworkFailureCount, Integer>> entry :
    465                 mSsidFailureCount.entrySet()) {
    466             final AvailableNetworkFailureCount failureCount = entry.getValue().first;
    467             final Integer apCount = entry.getValue().second;
    468             sb.append("\n").append(entry.getKey()).append(": ").append(apCount).append(",")
    469                     .append(failureCount.toString());
    470         }
    471         return sb.toString();
    472     }
    473 
    474     /**
    475      * @param bssid bssid to check the failures for
    476      * @return true if any failure count is over FAILURE_THRESHOLD
    477      */
    478     public boolean isOverFailureThreshold(String bssid) {
    479         if ((getFailureCount(bssid, FAILURE_CODE_ASSOCIATION) >= FAILURE_THRESHOLD)
    480                 || (getFailureCount(bssid, FAILURE_CODE_AUTHENTICATION) >= FAILURE_THRESHOLD)
    481                 || (getFailureCount(bssid, FAILURE_CODE_DHCP) >= FAILURE_THRESHOLD)) {
    482             return true;
    483         }
    484         return false;
    485     }
    486 
    487     /**
    488      * Get the failure count for a specific bssid. This actually checks the ssid attached to the
    489      * BSSID and returns the SSID count
    490      * @param reason failure reason to get count for
    491      */
    492     public int getFailureCount(String bssid, int reason) {
    493         AvailableNetworkFailureCount availableNetworkFailureCount =
    494                 mRecentAvailableNetworks.get(bssid);
    495         if (availableNetworkFailureCount == null) {
    496             return 0;
    497         }
    498         String ssid = availableNetworkFailureCount.ssid;
    499         Pair<AvailableNetworkFailureCount, Integer> ssidFails = mSsidFailureCount.get(ssid);
    500         if (ssidFails == null) {
    501             Log.d(TAG, "getFailureCount: Could not find SSID count for " + ssid);
    502             return 0;
    503         }
    504         final AvailableNetworkFailureCount failCount = ssidFails.first;
    505         switch (reason) {
    506             case FAILURE_CODE_ASSOCIATION:
    507                 return failCount.associationRejection;
    508             case FAILURE_CODE_AUTHENTICATION:
    509                 return failCount.authenticationFailure;
    510             case FAILURE_CODE_DHCP:
    511                 return failCount.dhcpFailure;
    512             default:
    513                 return 0;
    514         }
    515     }
    516 
    517     protected void enableVerboseLogging(int verbose) {
    518         if (verbose > 0) {
    519             mVerboseLoggingEnabled = true;
    520         } else {
    521             mVerboseLoggingEnabled = false;
    522         }
    523     }
    524 
    525     @VisibleForTesting
    526     protected void setBugReportProbability(double newProbability) {
    527         mBugReportProbability = newProbability;
    528     }
    529 
    530     /**
    531      * This class holds the failure counts for an 'available network' (one of the potential
    532      * candidates for connection, as determined by framework).
    533      */
    534     public static class AvailableNetworkFailureCount {
    535         /**
    536          * WifiConfiguration associated with this network. Can be null for Ephemeral networks
    537          */
    538         public WifiConfiguration config;
    539         /**
    540         * SSID of the network (from ScanDetail)
    541         */
    542         public String ssid = "";
    543         /**
    544          * Number of times network has failed due to Association Rejection
    545          */
    546         public int associationRejection = 0;
    547         /**
    548          * Number of times network has failed due to Authentication Failure or SSID_TEMP_DISABLED
    549          */
    550         public int authenticationFailure = 0;
    551         /**
    552          * Number of times network has failed due to DHCP failure
    553          */
    554         public int dhcpFailure = 0;
    555         /**
    556          * Number of scanResults since this network was last seen
    557          */
    558         public int age = 0;
    559 
    560         AvailableNetworkFailureCount(WifiConfiguration configParam) {
    561             this.config = configParam;
    562         }
    563 
    564         /**
    565          * @param reason failure reason to increment count for
    566          */
    567         public void incrementFailureCount(int reason) {
    568             switch (reason) {
    569                 case FAILURE_CODE_ASSOCIATION:
    570                     associationRejection++;
    571                     break;
    572                 case FAILURE_CODE_AUTHENTICATION:
    573                     authenticationFailure++;
    574                     break;
    575                 case FAILURE_CODE_DHCP:
    576                     dhcpFailure++;
    577                     break;
    578                 default: //do nothing
    579             }
    580         }
    581 
    582         /**
    583          * Set all failure counts for this network to 0
    584          */
    585         void resetCounts() {
    586             associationRejection = 0;
    587             authenticationFailure = 0;
    588             dhcpFailure = 0;
    589         }
    590 
    591         public String toString() {
    592             return  ssid + " HasEverConnected: " + ((config != null)
    593                     ? config.getNetworkSelectionStatus().getHasEverConnected() : "null_config")
    594                     + ", Failures: {"
    595                     + "Assoc: " + associationRejection
    596                     + ", Auth: " + authenticationFailure
    597                     + ", Dhcp: " + dhcpFailure
    598                     + "}";
    599         }
    600     }
    601 }
    602