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