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