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