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