WifiConnectivityManager.java revision 6259b630ddb59b642729a2d2113d81ed8e33a0e3
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 17package com.android.server.wifi; 18 19import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE; 20 21import android.app.ActivityManager; 22import android.app.AlarmManager; 23import android.content.Context; 24import android.net.wifi.ScanResult; 25import android.net.wifi.SupplicantState; 26import android.net.wifi.WifiConfiguration; 27import android.net.wifi.WifiInfo; 28import android.net.wifi.WifiManager; 29import android.net.wifi.WifiScanner; 30import android.net.wifi.WifiScanner.PnoSettings; 31import android.net.wifi.WifiScanner.ScanSettings; 32import android.os.Handler; 33import android.os.Looper; 34import android.util.LocalLog; 35import android.util.Log; 36 37import com.android.internal.R; 38import com.android.internal.annotations.VisibleForTesting; 39import com.android.server.wifi.util.ScanResultUtil; 40 41import java.io.FileDescriptor; 42import java.io.PrintWriter; 43import java.util.ArrayList; 44import java.util.Iterator; 45import java.util.LinkedList; 46import java.util.List; 47import java.util.Set; 48 49/** 50 * This class manages all the connectivity related scanning activities. 51 * 52 * When the screen is turned on or off, WiFi is connected or disconnected, 53 * or on-demand, a scan is initiatiated and the scan results are passed 54 * to QNS for it to make a recommendation on which network to connect to. 55 */ 56public class WifiConnectivityManager { 57 public static final String WATCHDOG_TIMER_TAG = 58 "WifiConnectivityManager Schedule Watchdog Timer"; 59 public static final String PERIODIC_SCAN_TIMER_TAG = 60 "WifiConnectivityManager Schedule Periodic Scan Timer"; 61 public static final String RESTART_SINGLE_SCAN_TIMER_TAG = 62 "WifiConnectivityManager Restart Single Scan"; 63 public static final String RESTART_CONNECTIVITY_SCAN_TIMER_TAG = 64 "WifiConnectivityManager Restart Scan"; 65 66 private static final String TAG = "WifiConnectivityManager"; 67 private static final long RESET_TIME_STAMP = Long.MIN_VALUE; 68 // Constants to indicate whether a scan should start immediately or 69 // it should comply to the minimum scan interval rule. 70 private static final boolean SCAN_IMMEDIATELY = true; 71 private static final boolean SCAN_ON_SCHEDULE = false; 72 // Periodic scan interval in milli-seconds. This is the scan 73 // performed when screen is on. 74 @VisibleForTesting 75 public static final int PERIODIC_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds 76 // When screen is on and WiFi traffic is heavy, exponential backoff 77 // connectivity scans are scheduled. This constant defines the maximum 78 // scan interval in this scenario. 79 @VisibleForTesting 80 public static final int MAX_PERIODIC_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds 81 // PNO scan interval in milli-seconds. This is the scan 82 // performed when screen is off and disconnected. 83 private static final int DISCONNECTED_PNO_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds 84 // PNO scan interval in milli-seconds. This is the scan 85 // performed when screen is off and connected. 86 private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds 87 // When a network is found by PNO scan but gets rejected by QNS due to its 88 // low RSSI value, scan will be reschduled in an exponential back off manner. 89 private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds 90 private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds 91 // Maximum number of retries when starting a scan failed 92 @VisibleForTesting 93 public static final int MAX_SCAN_RESTART_ALLOWED = 5; 94 // Number of milli-seconds to delay before retry starting 95 // a previously failed scan 96 private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds 97 // When in disconnected mode, a watchdog timer will be fired 98 // every WATCHDOG_INTERVAL_MS to start a single scan. This is 99 // to prevent caveat from things like PNO scan. 100 private static final int WATCHDOG_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes 101 // Restricted channel list age out value. 102 private static final int CHANNEL_LIST_AGE_MS = 60 * 60 * 1000; // 1 hour 103 // This is the time interval for the connection attempt rate calculation. Connection attempt 104 // timestamps beyond this interval is evicted from the list. 105 public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins 106 // Max number of connection attempts in the above time interval. 107 public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6; 108 // Packet tx/rx rates to determine if we want to do partial vs full scans. 109 // TODO(b/31180330): Make these device configs. 110 public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8; 111 public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16; 112 113 // WifiStateMachine has a bunch of states. From the 114 // WifiConnectivityManager's perspective it only cares 115 // if it is in Connected state, Disconnected state or in 116 // transition between these two states. 117 public static final int WIFI_STATE_UNKNOWN = 0; 118 public static final int WIFI_STATE_CONNECTED = 1; 119 public static final int WIFI_STATE_DISCONNECTED = 2; 120 public static final int WIFI_STATE_TRANSITIONING = 3; 121 122 // Due to b/28020168, timer based single scan will be scheduled 123 // to provide periodic scan in an exponential backoff fashion. 124 private static final boolean ENABLE_BACKGROUND_SCAN = false; 125 // Flag to turn on connected PNO, when needed 126 private static final boolean ENABLE_CONNECTED_PNO_SCAN = false; 127 128 private final WifiStateMachine mStateMachine; 129 private final WifiScanner mScanner; 130 private final WifiConfigManager mConfigManager; 131 private final WifiInfo mWifiInfo; 132 private final WifiQualifiedNetworkSelector mQualifiedNetworkSelector; 133 private final WifiLastResortWatchdog mWifiLastResortWatchdog; 134 private final WifiMetrics mWifiMetrics; 135 private final AlarmManager mAlarmManager; 136 private final Handler mEventHandler; 137 private final Clock mClock; 138 private final LocalLog mLocalLog = 139 new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256); 140 private final LinkedList<Long> mConnectionAttemptTimeStamps; 141 142 private boolean mDbg = false; 143 private boolean mWifiEnabled = false; 144 private boolean mWifiConnectivityManagerEnabled = true; 145 private boolean mScreenOn = false; 146 private int mWifiState = WIFI_STATE_UNKNOWN; 147 private boolean mUntrustedConnectionAllowed = false; 148 private int mScanRestartCount = 0; 149 private int mSingleScanRestartCount = 0; 150 private int mTotalConnectivityAttemptsRateLimited = 0; 151 private String mLastConnectionAttemptBssid = null; 152 private int mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS; 153 private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP; 154 private boolean mPnoScanStarted = false; 155 private boolean mPeriodicScanTimerSet = false; 156 // Device configs 157 private boolean mEnableAutoJoinWhenAssociated; 158 // PNO settings 159 private int mMin5GHzRssi; 160 private int mMin24GHzRssi; 161 private int mInitialScoreMax; 162 private int mCurrentConnectionBonus; 163 private int mSameNetworkBonus; 164 private int mSecureBonus; 165 private int mBand5GHzBonus; 166 167 // A helper to log debugging information in the local log buffer, which can 168 // be retrieved in bugreport. 169 private void localLog(String log) { 170 mLocalLog.log(log); 171 } 172 173 // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times 174 // if the start scan command failed. An timer is used here to make it a deferred retry. 175 private final AlarmManager.OnAlarmListener mRestartScanListener = 176 new AlarmManager.OnAlarmListener() { 177 public void onAlarm() { 178 startConnectivityScan(SCAN_IMMEDIATELY); 179 } 180 }; 181 182 // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times 183 // if the start scan command failed. An timer is used here to make it a deferred retry. 184 private class RestartSingleScanListener implements AlarmManager.OnAlarmListener { 185 private final boolean mIsFullBandScan; 186 187 RestartSingleScanListener(boolean isFullBandScan) { 188 mIsFullBandScan = isFullBandScan; 189 } 190 191 @Override 192 public void onAlarm() { 193 startSingleScan(mIsFullBandScan); 194 } 195 } 196 197 // As a watchdog mechanism, a single scan will be scheduled every WATCHDOG_INTERVAL_MS 198 // if it is in the WIFI_STATE_DISCONNECTED state. 199 private final AlarmManager.OnAlarmListener mWatchdogListener = 200 new AlarmManager.OnAlarmListener() { 201 public void onAlarm() { 202 watchdogHandler(); 203 } 204 }; 205 206 // Due to b/28020168, timer based single scan will be scheduled 207 // to provide periodic scan in an exponential backoff fashion. 208 private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener = 209 new AlarmManager.OnAlarmListener() { 210 public void onAlarm() { 211 periodicScanTimerHandler(); 212 } 213 }; 214 215 /** 216 * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener. 217 * Executes selection of potential network candidates, initiation of connection attempt to that 218 * network. 219 * 220 * @return true - if a candidate is selected by QNS 221 * false - if no candidate is selected by QNS 222 */ 223 private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) { 224 localLog(listenerName + " onResults: start QNS"); 225 WifiConfiguration candidate = 226 mQualifiedNetworkSelector.selectQualifiedNetwork(false, 227 mUntrustedConnectionAllowed, mStateMachine.isLinkDebouncing(), 228 mStateMachine.isConnected(), mStateMachine.isDisconnected(), 229 mStateMachine.isSupplicantTransientState(), scanDetails); 230 mWifiLastResortWatchdog.updateAvailableNetworks( 231 mQualifiedNetworkSelector.getFilteredScanDetails()); 232 mWifiMetrics.countScanResults(scanDetails); 233 if (candidate != null) { 234 localLog(listenerName + ": QNS candidate-" + candidate.SSID); 235 connectToNetwork(candidate); 236 return true; 237 } else { 238 return false; 239 } 240 } 241 242 // Periodic scan results listener. A periodic scan is initiated when 243 // screen is on. 244 private class PeriodicScanListener implements WifiScanner.ScanListener { 245 private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); 246 247 public void clearScanDetails() { 248 mScanDetails.clear(); 249 } 250 251 @Override 252 public void onSuccess() { 253 localLog("PeriodicScanListener onSuccess"); 254 } 255 256 @Override 257 public void onFailure(int reason, String description) { 258 Log.e(TAG, "PeriodicScanListener onFailure:" 259 + " reason: " + reason 260 + " description: " + description); 261 262 // reschedule the scan 263 if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { 264 scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS); 265 } else { 266 mScanRestartCount = 0; 267 Log.e(TAG, "Failed to successfully start periodic scan for " 268 + MAX_SCAN_RESTART_ALLOWED + " times"); 269 } 270 } 271 272 @Override 273 public void onPeriodChanged(int periodInMs) { 274 localLog("PeriodicScanListener onPeriodChanged: " 275 + "actual scan period " + periodInMs + "ms"); 276 } 277 278 @Override 279 public void onResults(WifiScanner.ScanData[] results) { 280 handleScanResults(mScanDetails, "PeriodicScanListener"); 281 clearScanDetails(); 282 mScanRestartCount = 0; 283 } 284 285 @Override 286 public void onFullResult(ScanResult fullScanResult) { 287 if (mDbg) { 288 localLog("PeriodicScanListener onFullResult: " 289 + fullScanResult.SSID + " capabilities " 290 + fullScanResult.capabilities); 291 } 292 293 mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult)); 294 } 295 } 296 297 private final PeriodicScanListener mPeriodicScanListener = new PeriodicScanListener(); 298 299 // All single scan results listener. 300 // 301 // Note: This is the listener for all the available single scan results, 302 // including the ones initiated by WifiConnectivityManager and 303 // other modules. 304 private class AllSingleScanListener implements WifiScanner.ScanListener { 305 private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); 306 307 public void clearScanDetails() { 308 mScanDetails.clear(); 309 } 310 311 @Override 312 public void onSuccess() { 313 localLog("registerScanListener onSuccess"); 314 } 315 316 @Override 317 public void onFailure(int reason, String description) { 318 Log.e(TAG, "registerScanListener onFailure:" 319 + " reason: " + reason 320 + " description: " + description); 321 } 322 323 @Override 324 public void onPeriodChanged(int periodInMs) { 325 } 326 327 @Override 328 public void onResults(WifiScanner.ScanData[] results) { 329 if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) { 330 clearScanDetails(); 331 return; 332 } 333 334 boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener"); 335 clearScanDetails(); 336 337 // Update metrics to see if a single scan detected a valid network 338 // while PNO scan didn't. 339 // Note: We don't update the background scan metrics any more as it is 340 // not in use. 341 if (mPnoScanStarted) { 342 if (wasConnectAttempted) { 343 mWifiMetrics.incrementNumConnectivityWatchdogPnoBad(); 344 } else { 345 mWifiMetrics.incrementNumConnectivityWatchdogPnoGood(); 346 } 347 } 348 } 349 350 @Override 351 public void onFullResult(ScanResult fullScanResult) { 352 if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) { 353 return; 354 } 355 356 if (mDbg) { 357 localLog("AllSingleScanListener onFullResult: " 358 + fullScanResult.SSID + " capabilities " 359 + fullScanResult.capabilities); 360 } 361 362 mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult)); 363 } 364 } 365 366 private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener(); 367 368 // Single scan results listener. A single scan is initiated when 369 // Disconnected/ConnectedPNO scan found a valid network and woke up 370 // the system, or by the watchdog timer, or to form the timer based 371 // periodic scan. 372 // 373 // Note: This is the listener for the single scans initiated by the 374 // WifiConnectivityManager. 375 private class SingleScanListener implements WifiScanner.ScanListener { 376 private final boolean mIsFullBandScan; 377 378 SingleScanListener(boolean isFullBandScan) { 379 mIsFullBandScan = isFullBandScan; 380 } 381 382 @Override 383 public void onSuccess() { 384 localLog("SingleScanListener onSuccess"); 385 } 386 387 @Override 388 public void onFailure(int reason, String description) { 389 Log.e(TAG, "SingleScanListener onFailure:" 390 + " reason: " + reason 391 + " description: " + description); 392 393 // reschedule the scan 394 if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { 395 scheduleDelayedSingleScan(mIsFullBandScan); 396 } else { 397 mSingleScanRestartCount = 0; 398 Log.e(TAG, "Failed to successfully start single scan for " 399 + MAX_SCAN_RESTART_ALLOWED + " times"); 400 } 401 } 402 403 @Override 404 public void onPeriodChanged(int periodInMs) { 405 localLog("SingleScanListener onPeriodChanged: " 406 + "actual scan period " + periodInMs + "ms"); 407 } 408 409 @Override 410 public void onResults(WifiScanner.ScanData[] results) { 411 } 412 413 @Override 414 public void onFullResult(ScanResult fullScanResult) { 415 } 416 } 417 418 // re-enable this when b/27695292 is fixed 419 // private final SingleScanListener mSingleScanListener = new SingleScanListener(); 420 421 // PNO scan results listener for both disconected and connected PNO scanning. 422 // A PNO scan is initiated when screen is off. 423 private class PnoScanListener implements WifiScanner.PnoScanListener { 424 private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>(); 425 private int mLowRssiNetworkRetryDelay = 426 LOW_RSSI_NETWORK_RETRY_START_DELAY_MS; 427 428 public void clearScanDetails() { 429 mScanDetails.clear(); 430 } 431 432 // Reset to the start value when either a non-PNO scan is started or 433 // QNS selects a candidate from the PNO scan results. 434 public void resetLowRssiNetworkRetryDelay() { 435 mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS; 436 } 437 438 @VisibleForTesting 439 public int getLowRssiNetworkRetryDelay() { 440 return mLowRssiNetworkRetryDelay; 441 } 442 443 @Override 444 public void onSuccess() { 445 localLog("PnoScanListener onSuccess"); 446 } 447 448 @Override 449 public void onFailure(int reason, String description) { 450 Log.e(TAG, "PnoScanListener onFailure:" 451 + " reason: " + reason 452 + " description: " + description); 453 454 // reschedule the scan 455 if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) { 456 scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS); 457 } else { 458 mScanRestartCount = 0; 459 Log.e(TAG, "Failed to successfully start PNO scan for " 460 + MAX_SCAN_RESTART_ALLOWED + " times"); 461 } 462 } 463 464 @Override 465 public void onPeriodChanged(int periodInMs) { 466 localLog("PnoScanListener onPeriodChanged: " 467 + "actual scan period " + periodInMs + "ms"); 468 } 469 470 // Currently the PNO scan results doesn't include IE, 471 // which contains information required by QNS. Ignore them 472 // for now. 473 @Override 474 public void onResults(WifiScanner.ScanData[] results) { 475 } 476 477 @Override 478 public void onFullResult(ScanResult fullScanResult) { 479 } 480 481 @Override 482 public void onPnoNetworkFound(ScanResult[] results) { 483 localLog("PnoScanListener: onPnoNetworkFound: results len = " + results.length); 484 485 for (ScanResult result: results) { 486 mScanDetails.add(ScanResultUtil.toScanDetail(result)); 487 } 488 489 boolean wasConnectAttempted; 490 wasConnectAttempted = handleScanResults(mScanDetails, "PnoScanListener"); 491 clearScanDetails(); 492 mScanRestartCount = 0; 493 494 if (!wasConnectAttempted) { 495 // The scan results were rejected by QNS due to low RSSI values 496 if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) { 497 mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS; 498 } 499 scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelay); 500 501 // Set up the delay value for next retry. 502 mLowRssiNetworkRetryDelay *= 2; 503 } else { 504 resetLowRssiNetworkRetryDelay(); 505 } 506 } 507 } 508 509 private final PnoScanListener mPnoScanListener = new PnoScanListener(); 510 511 /** 512 * WifiConnectivityManager constructor 513 */ 514 public WifiConnectivityManager(Context context, WifiStateMachine stateMachine, 515 WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo, 516 WifiQualifiedNetworkSelector qualifiedNetworkSelector, 517 WifiInjector wifiInjector, Looper looper, boolean enable) { 518 mStateMachine = stateMachine; 519 mScanner = scanner; 520 mConfigManager = configManager; 521 mWifiInfo = wifiInfo; 522 mQualifiedNetworkSelector = qualifiedNetworkSelector; 523 mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog(); 524 mWifiMetrics = wifiInjector.getWifiMetrics(); 525 mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 526 mEventHandler = new Handler(looper); 527 mClock = wifiInjector.getClock(); 528 mConnectionAttemptTimeStamps = new LinkedList<>(); 529 530 mMin5GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI; 531 mMin24GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI; 532 mBand5GHzBonus = WifiQualifiedNetworkSelector.BAND_AWARD_5GHz; 533 534 mCurrentConnectionBonus = context.getResources().getInteger( 535 R.integer.config_wifi_framework_current_network_boost); 536 mSameNetworkBonus = context.getResources().getInteger( 537 R.integer.config_wifi_framework_SAME_BSSID_AWARD); 538 mSecureBonus = context.getResources().getInteger( 539 R.integer.config_wifi_framework_SECURITY_AWARD); 540 int thresholdSaturatedRssi24 = context.getResources().getInteger( 541 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); 542 mInitialScoreMax = 543 (thresholdSaturatedRssi24 + WifiQualifiedNetworkSelector.RSSI_SCORE_OFFSET) 544 * WifiQualifiedNetworkSelector.RSSI_SCORE_SLOPE; 545 mEnableAutoJoinWhenAssociated = context.getResources().getBoolean( 546 R.bool.config_wifi_framework_enable_associated_network_selection); 547 548 Log.i(TAG, "PNO settings:" + " min5GHzRssi " + mMin5GHzRssi 549 + " min24GHzRssi " + mMin24GHzRssi 550 + " currentConnectionBonus " + mCurrentConnectionBonus 551 + " sameNetworkBonus " + mSameNetworkBonus 552 + " secureNetworkBonus " + mSecureBonus 553 + " initialScoreMax " + mInitialScoreMax); 554 555 // Register for all single scan results 556 mScanner.registerScanListener(mAllSingleScanListener); 557 558 mWifiConnectivityManagerEnabled = enable; 559 560 Log.i(TAG, "ConnectivityScanManager initialized and " 561 + (enable ? "enabled" : "disabled")); 562 } 563 564 /** 565 * This checks the connection attempt rate and recommends whether the connection attempt 566 * should be skipped or not. This attempts to rate limit the rate of connections to 567 * prevent us from flapping between networks and draining battery rapidly. 568 */ 569 private boolean shouldSkipConnectionAttempt(Long timeMillis) { 570 Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator(); 571 // First evict old entries from the queue. 572 while (attemptIter.hasNext()) { 573 Long connectionAttemptTimeMillis = attemptIter.next(); 574 if ((timeMillis - connectionAttemptTimeMillis) 575 > MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS) { 576 attemptIter.remove(); 577 } else { 578 // This list is sorted by timestamps, so we can skip any more checks 579 break; 580 } 581 } 582 // If we've reached the max connection attempt rate, skip this connection attempt 583 return (mConnectionAttemptTimeStamps.size() >= MAX_CONNECTION_ATTEMPTS_RATE); 584 } 585 586 /** 587 * Add the current connection attempt timestamp to our queue of connection attempts. 588 */ 589 private void noteConnectionAttempt(Long timeMillis) { 590 mConnectionAttemptTimeStamps.addLast(timeMillis); 591 } 592 593 /** 594 * This is used to clear the connection attempt rate limiter. This is done when the user 595 * explicitly tries to connect to a specified network. 596 */ 597 private void clearConnectionAttemptTimeStamps() { 598 mConnectionAttemptTimeStamps.clear(); 599 } 600 601 /** 602 * Attempt to connect to a network candidate. 603 * 604 * Based on the currently connected network, this menthod determines whether we should 605 * connect or roam to the network candidate recommended by QNS. 606 */ 607 private void connectToNetwork(WifiConfiguration candidate) { 608 ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate(); 609 if (scanResultCandidate == null) { 610 Log.e(TAG, "connectToNetwork: bad candidate - " + candidate 611 + " scanResult: " + scanResultCandidate); 612 return; 613 } 614 615 String targetBssid = scanResultCandidate.BSSID; 616 String targetAssociationId = candidate.SSID + " : " + targetBssid; 617 618 // Check if we are already connected or in the process of connecting to the target 619 // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just 620 // in case the firmware automatically roamed to a BSSID different from what QNS 621 // selected. 622 if (targetBssid != null 623 && (targetBssid.equals(mLastConnectionAttemptBssid) 624 || targetBssid.equals(mWifiInfo.getBSSID())) 625 && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) { 626 localLog("connectToNetwork: Either already connected " 627 + "or is connecting to " + targetAssociationId); 628 return; 629 } 630 631 Long elapsedTimeMillis = mClock.getElapsedSinceBootMillis(); 632 if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) { 633 localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!"); 634 mTotalConnectivityAttemptsRateLimited++; 635 return; 636 } 637 noteConnectionAttempt(elapsedTimeMillis); 638 639 mLastConnectionAttemptBssid = targetBssid; 640 641 WifiConfiguration currentConnectedNetwork = mConfigManager 642 .getConfiguredNetwork(mWifiInfo.getNetworkId()); 643 String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" : 644 (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID()); 645 646 if (currentConnectedNetwork != null 647 && (currentConnectedNetwork.networkId == candidate.networkId 648 || currentConnectedNetwork.isLinked(candidate))) { 649 localLog("connectToNetwork: Roaming from " + currentAssociationId + " to " 650 + targetAssociationId); 651 mStateMachine.startRoamToNetwork(candidate.networkId, scanResultCandidate); 652 } else { 653 localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to " 654 + targetAssociationId); 655 mStateMachine.startConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID); 656 } 657 } 658 659 // Helper for selecting the band for connectivity scan 660 private int getScanBand() { 661 return getScanBand(true); 662 } 663 664 private int getScanBand(boolean isFullBandScan) { 665 if (isFullBandScan) { 666 int freqBand = mStateMachine.getFrequencyBand(); 667 if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_5GHZ) { 668 return WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS; 669 } else if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ) { 670 return WifiScanner.WIFI_BAND_24_GHZ; 671 } else { 672 return WifiScanner.WIFI_BAND_BOTH_WITH_DFS; 673 } 674 } else { 675 // Use channel list instead. 676 return WifiScanner.WIFI_BAND_UNSPECIFIED; 677 } 678 } 679 680 // Helper for setting the channels for connectivity scan when band is unspecified. Returns 681 // false if we can't retrieve the info. 682 private boolean setScanChannels(ScanSettings settings) { 683 WifiConfiguration config = mStateMachine.getCurrentWifiConfiguration(); 684 685 if (config == null) { 686 return false; 687 } 688 689 Set<Integer> freqs = 690 mConfigManager.fetchChannelSetForNetworkForPartialScan( 691 config.networkId, CHANNEL_LIST_AGE_MS); 692 693 if (freqs != null && freqs.size() != 0) { 694 int index = 0; 695 settings.channels = new WifiScanner.ChannelSpec[freqs.size()]; 696 for (Integer freq : freqs) { 697 settings.channels[index++] = new WifiScanner.ChannelSpec(freq); 698 } 699 return true; 700 } else { 701 localLog("No scan channels for " + config.configKey() + ". Perform full band scan"); 702 return false; 703 } 704 } 705 706 // Watchdog timer handler 707 private void watchdogHandler() { 708 localLog("watchdogHandler"); 709 710 // Schedule the next timer and start a single scan if we are in disconnected state. 711 // Otherwise, the watchdog timer will be scheduled when entering disconnected 712 // state. 713 if (mWifiState == WIFI_STATE_DISCONNECTED) { 714 Log.i(TAG, "start a single scan from watchdogHandler"); 715 716 scheduleWatchdogTimer(); 717 startSingleScan(true); 718 } 719 } 720 721 // Start a single scan and set up the interval for next single scan. 722 private void startPeriodicSingleScan() { 723 long currentTimeStamp = mClock.getElapsedSinceBootMillis(); 724 725 if (mLastPeriodicSingleScanTimeStamp != RESET_TIME_STAMP) { 726 long msSinceLastScan = currentTimeStamp - mLastPeriodicSingleScanTimeStamp; 727 if (msSinceLastScan < PERIODIC_SCAN_INTERVAL_MS) { 728 localLog("Last periodic single scan started " + msSinceLastScan 729 + "ms ago, defer this new scan request."); 730 schedulePeriodicScanTimer(PERIODIC_SCAN_INTERVAL_MS - (int) msSinceLastScan); 731 return; 732 } 733 } 734 735 boolean isFullBandScan = true; 736 737 // If the WiFi traffic is heavy, only partial scan is initiated. 738 if (mWifiState == WIFI_STATE_CONNECTED 739 && (mWifiInfo.txSuccessRate > MAX_TX_PACKET_FOR_FULL_SCANS 740 || mWifiInfo.rxSuccessRate > MAX_RX_PACKET_FOR_FULL_SCANS)) { 741 localLog("No full band scan due to heavy traffic, txSuccessRate=" 742 + mWifiInfo.txSuccessRate + " rxSuccessRate=" 743 + mWifiInfo.rxSuccessRate); 744 isFullBandScan = false; 745 } 746 747 mLastPeriodicSingleScanTimeStamp = currentTimeStamp; 748 startSingleScan(isFullBandScan); 749 schedulePeriodicScanTimer(mPeriodicSingleScanInterval); 750 751 // Set up the next scan interval in an exponential backoff fashion. 752 mPeriodicSingleScanInterval *= 2; 753 if (mPeriodicSingleScanInterval > MAX_PERIODIC_SCAN_INTERVAL_MS) { 754 mPeriodicSingleScanInterval = MAX_PERIODIC_SCAN_INTERVAL_MS; 755 } 756 } 757 758 // Reset the last periodic single scan time stamp so that the next periodic single 759 // scan can start immediately. 760 private void resetLastPeriodicSingleScanTimeStamp() { 761 mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP; 762 } 763 764 // Periodic scan timer handler 765 private void periodicScanTimerHandler() { 766 localLog("periodicScanTimerHandler"); 767 768 // Schedule the next timer and start a single scan if screen is on. 769 if (mScreenOn) { 770 startPeriodicSingleScan(); 771 } 772 } 773 774 // Start a single scan 775 private void startSingleScan(boolean isFullBandScan) { 776 if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) { 777 return; 778 } 779 780 mPnoScanListener.resetLowRssiNetworkRetryDelay(); 781 782 ScanSettings settings = new ScanSettings(); 783 if (!isFullBandScan) { 784 if (!setScanChannels(settings)) { 785 isFullBandScan = true; 786 } 787 } 788 settings.band = getScanBand(isFullBandScan); 789 settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT 790 | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; 791 settings.numBssidsPerScan = 0; 792 793 List<ScanSettings.HiddenNetwork> hiddenNetworkList = 794 mConfigManager.retrieveHiddenNetworkList(); 795 settings.hiddenNetworks = 796 hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[hiddenNetworkList.size()]); 797 798 // re-enable this when b/27695292 is fixed 799 // mSingleScanListener.clearScanDetails(); 800 // mScanner.startScan(settings, mSingleScanListener, WIFI_WORK_SOURCE); 801 SingleScanListener singleScanListener = 802 new SingleScanListener(isFullBandScan); 803 mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE); 804 } 805 806 // Start a periodic scan when screen is on 807 private void startPeriodicScan(boolean scanImmediately) { 808 mPnoScanListener.resetLowRssiNetworkRetryDelay(); 809 810 // No connectivity scan if auto roaming is disabled. 811 if (mWifiState == WIFI_STATE_CONNECTED && !mEnableAutoJoinWhenAssociated) { 812 return; 813 } 814 815 // Due to b/28020168, timer based single scan will be scheduled 816 // to provide periodic scan in an exponential backoff fashion. 817 if (!ENABLE_BACKGROUND_SCAN) { 818 if (scanImmediately) { 819 resetLastPeriodicSingleScanTimeStamp(); 820 } 821 mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS; 822 startPeriodicSingleScan(); 823 } else { 824 ScanSettings settings = new ScanSettings(); 825 settings.band = getScanBand(); 826 settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT 827 | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; 828 settings.numBssidsPerScan = 0; 829 settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS; 830 831 mPeriodicScanListener.clearScanDetails(); 832 mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE); 833 } 834 } 835 836 // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected 837 private void startDisconnectedPnoScan() { 838 // TODO(b/29503772): Need to change this interface. 839 840 // Initialize PNO settings 841 PnoSettings pnoSettings = new PnoSettings(); 842 List<PnoSettings.PnoNetwork> pnoNetworkList = mConfigManager.retrievePnoNetworkList(); 843 int listSize = pnoNetworkList.size(); 844 845 if (listSize == 0) { 846 // No saved network 847 localLog("No saved network for starting disconnected PNO."); 848 return; 849 } 850 851 pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize]; 852 pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList); 853 pnoSettings.min5GHzRssi = mMin5GHzRssi; 854 pnoSettings.min24GHzRssi = mMin24GHzRssi; 855 pnoSettings.initialScoreMax = mInitialScoreMax; 856 pnoSettings.currentConnectionBonus = mCurrentConnectionBonus; 857 pnoSettings.sameNetworkBonus = mSameNetworkBonus; 858 pnoSettings.secureBonus = mSecureBonus; 859 pnoSettings.band5GHzBonus = mBand5GHzBonus; 860 861 // Initialize scan settings 862 ScanSettings scanSettings = new ScanSettings(); 863 scanSettings.band = getScanBand(); 864 scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; 865 scanSettings.numBssidsPerScan = 0; 866 scanSettings.periodInMs = DISCONNECTED_PNO_SCAN_INTERVAL_MS; 867 // TODO: enable exponential back off scan later to further save energy 868 // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs; 869 870 mPnoScanListener.clearScanDetails(); 871 872 mScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener); 873 mPnoScanStarted = true; 874 } 875 876 // Start a ConnectedPNO scan when screen is off and Wifi is connected 877 private void startConnectedPnoScan() { 878 // TODO(b/29503772): Need to change this interface. 879 // Disable ConnectedPNO for now due to b/28020168 880 if (!ENABLE_CONNECTED_PNO_SCAN) { 881 return; 882 } 883 884 // Initialize PNO settings 885 PnoSettings pnoSettings = new PnoSettings(); 886 List<PnoSettings.PnoNetwork> pnoNetworkList = mConfigManager.retrievePnoNetworkList(); 887 int listSize = pnoNetworkList.size(); 888 889 if (listSize == 0) { 890 // No saved network 891 localLog("No saved network for starting connected PNO."); 892 return; 893 } 894 895 pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize]; 896 pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList); 897 pnoSettings.min5GHzRssi = mMin5GHzRssi; 898 pnoSettings.min24GHzRssi = mMin24GHzRssi; 899 pnoSettings.initialScoreMax = mInitialScoreMax; 900 pnoSettings.currentConnectionBonus = mCurrentConnectionBonus; 901 pnoSettings.sameNetworkBonus = mSameNetworkBonus; 902 pnoSettings.secureBonus = mSecureBonus; 903 pnoSettings.band5GHzBonus = mBand5GHzBonus; 904 905 // Initialize scan settings 906 ScanSettings scanSettings = new ScanSettings(); 907 scanSettings.band = getScanBand(); 908 scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; 909 scanSettings.numBssidsPerScan = 0; 910 scanSettings.periodInMs = CONNECTED_PNO_SCAN_INTERVAL_MS; 911 // TODO: enable exponential back off scan later to further save energy 912 // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs; 913 914 mPnoScanListener.clearScanDetails(); 915 916 mScanner.startConnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener); 917 mPnoScanStarted = true; 918 } 919 920 // Stop a PNO scan. This includes both DisconnectedPNO and ConnectedPNO scans. 921 private void stopPnoScan() { 922 if (mPnoScanStarted) { 923 mScanner.stopPnoScan(mPnoScanListener); 924 } 925 926 mPnoScanStarted = false; 927 } 928 929 // Set up watchdog timer 930 private void scheduleWatchdogTimer() { 931 Log.i(TAG, "scheduleWatchdogTimer"); 932 933 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 934 mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS, 935 WATCHDOG_TIMER_TAG, 936 mWatchdogListener, mEventHandler); 937 } 938 939 // Set up periodic scan timer 940 private void schedulePeriodicScanTimer(int intervalMs) { 941 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 942 mClock.getElapsedSinceBootMillis() + intervalMs, 943 PERIODIC_SCAN_TIMER_TAG, 944 mPeriodicScanTimerListener, mEventHandler); 945 mPeriodicScanTimerSet = true; 946 } 947 948 // Cancel periodic scan timer 949 private void cancelPeriodicScanTimer() { 950 if (mPeriodicScanTimerSet) { 951 mAlarmManager.cancel(mPeriodicScanTimerListener); 952 mPeriodicScanTimerSet = false; 953 } 954 } 955 956 // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS 957 private void scheduleDelayedSingleScan(boolean isFullBandScan) { 958 localLog("scheduleDelayedSingleScan"); 959 960 RestartSingleScanListener restartSingleScanListener = 961 new RestartSingleScanListener(isFullBandScan); 962 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 963 mClock.getElapsedSinceBootMillis() + RESTART_SCAN_DELAY_MS, 964 RESTART_SINGLE_SCAN_TIMER_TAG, 965 restartSingleScanListener, mEventHandler); 966 } 967 968 // Set up timer to start a delayed scan after msFromNow milli-seconds 969 private void scheduleDelayedConnectivityScan(int msFromNow) { 970 localLog("scheduleDelayedConnectivityScan"); 971 972 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 973 mClock.getElapsedSinceBootMillis() + msFromNow, 974 RESTART_CONNECTIVITY_SCAN_TIMER_TAG, 975 mRestartScanListener, mEventHandler); 976 977 } 978 979 // Start a connectivity scan. The scan method is chosen according to 980 // the current screen state and WiFi state. 981 private void startConnectivityScan(boolean scanImmediately) { 982 localLog("startConnectivityScan: screenOn=" + mScreenOn 983 + " wifiState=" + mWifiState 984 + " scanImmediately=" + scanImmediately 985 + " wifiEnabled=" + mWifiEnabled 986 + " wifiConnectivityManagerEnabled=" 987 + mWifiConnectivityManagerEnabled); 988 989 if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) { 990 return; 991 } 992 993 // Always stop outstanding connecivity scan if there is any 994 stopConnectivityScan(); 995 996 // Don't start a connectivity scan while Wifi is in the transition 997 // between connected and disconnected states. 998 if (mWifiState != WIFI_STATE_CONNECTED && mWifiState != WIFI_STATE_DISCONNECTED) { 999 return; 1000 } 1001 1002 if (mScreenOn) { 1003 startPeriodicScan(scanImmediately); 1004 } else { // screenOff 1005 if (mWifiState == WIFI_STATE_CONNECTED) { 1006 startConnectedPnoScan(); 1007 } else { 1008 startDisconnectedPnoScan(); 1009 } 1010 } 1011 } 1012 1013 // Stop connectivity scan if there is any. 1014 private void stopConnectivityScan() { 1015 // Due to b/28020168, timer based single scan will be scheduled 1016 // to provide periodic scan in an exponential backoff fashion. 1017 if (!ENABLE_BACKGROUND_SCAN) { 1018 cancelPeriodicScanTimer(); 1019 } else { 1020 mScanner.stopBackgroundScan(mPeriodicScanListener); 1021 } 1022 stopPnoScan(); 1023 mScanRestartCount = 0; 1024 } 1025 1026 /** 1027 * Handler for screen state (on/off) changes 1028 */ 1029 public void handleScreenStateChanged(boolean screenOn) { 1030 localLog("handleScreenStateChanged: screenOn=" + screenOn); 1031 1032 mScreenOn = screenOn; 1033 1034 startConnectivityScan(SCAN_ON_SCHEDULE); 1035 } 1036 1037 /** 1038 * Handler for WiFi state (connected/disconnected) changes 1039 */ 1040 public void handleConnectionStateChanged(int state) { 1041 localLog("handleConnectionStateChanged: state=" + state); 1042 1043 mWifiState = state; 1044 1045 // Reset BSSID of last connection attempt and kick off 1046 // the watchdog timer if entering disconnected state. 1047 if (mWifiState == WIFI_STATE_DISCONNECTED) { 1048 mLastConnectionAttemptBssid = null; 1049 scheduleWatchdogTimer(); 1050 } 1051 1052 startConnectivityScan(SCAN_ON_SCHEDULE); 1053 } 1054 1055 /** 1056 * Handler when user toggles whether untrusted connection is allowed 1057 */ 1058 public void setUntrustedConnectionAllowed(boolean allowed) { 1059 Log.i(TAG, "setUntrustedConnectionAllowed: allowed=" + allowed); 1060 1061 if (mUntrustedConnectionAllowed != allowed) { 1062 mUntrustedConnectionAllowed = allowed; 1063 startConnectivityScan(SCAN_IMMEDIATELY); 1064 } 1065 } 1066 1067 /** 1068 * Handler when user specifies a particular network to connect to 1069 */ 1070 public void setUserConnectChoice(int netId) { 1071 Log.i(TAG, "setUserConnectChoice: netId=" + netId); 1072 1073 mQualifiedNetworkSelector.setUserConnectChoice(netId); 1074 1075 clearConnectionAttemptTimeStamps(); 1076 } 1077 1078 /** 1079 * Handler for on-demand connectivity scan 1080 */ 1081 public void forceConnectivityScan() { 1082 Log.i(TAG, "forceConnectivityScan"); 1083 1084 startConnectivityScan(SCAN_IMMEDIATELY); 1085 } 1086 1087 /** 1088 * Track whether a BSSID should be enabled or disabled for QNS 1089 */ 1090 public boolean trackBssid(String bssid, boolean enable) { 1091 Log.i(TAG, "trackBssid: " + (enable ? "enable " : "disable ") + bssid); 1092 1093 boolean ret = mQualifiedNetworkSelector 1094 .enableBssidForQualityNetworkSelection(bssid, enable); 1095 1096 if (ret && !enable) { 1097 // Disabling a BSSID can happen when the AP candidate to connect to has 1098 // no capacity for new stations. We start another scan immediately so that QNS 1099 // can give us another candidate to connect to. 1100 startConnectivityScan(SCAN_IMMEDIATELY); 1101 } 1102 1103 return ret; 1104 } 1105 1106 /** 1107 * Set band preference when doing scan and making connection 1108 */ 1109 public void setUserPreferredBand(int band) { 1110 Log.i(TAG, "User band preference: " + band); 1111 1112 mQualifiedNetworkSelector.setUserPreferredBand(band); 1113 startConnectivityScan(SCAN_IMMEDIATELY); 1114 } 1115 1116 /** 1117 * Inform WiFi is enabled for connection or not 1118 */ 1119 public void setWifiEnabled(boolean enable) { 1120 Log.i(TAG, "Set WiFi " + (enable ? "enabled" : "disabled")); 1121 1122 mWifiEnabled = enable; 1123 1124 if (!mWifiEnabled) { 1125 stopConnectivityScan(); 1126 resetLastPeriodicSingleScanTimeStamp(); 1127 mLastConnectionAttemptBssid = null; 1128 } else if (mWifiConnectivityManagerEnabled) { 1129 startConnectivityScan(SCAN_IMMEDIATELY); 1130 } 1131 } 1132 1133 /** 1134 * Turn on/off the WifiConnectivityMangager at runtime 1135 */ 1136 public void enable(boolean enable) { 1137 Log.i(TAG, "Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled")); 1138 1139 mWifiConnectivityManagerEnabled = enable; 1140 1141 if (!mWifiConnectivityManagerEnabled) { 1142 stopConnectivityScan(); 1143 resetLastPeriodicSingleScanTimeStamp(); 1144 mLastConnectionAttemptBssid = null; 1145 } else if (mWifiEnabled) { 1146 startConnectivityScan(SCAN_IMMEDIATELY); 1147 } 1148 } 1149 1150 /** 1151 * Enable/disable verbose logging 1152 */ 1153 public void enableVerboseLogging(int verbose) { 1154 mDbg = verbose > 0; 1155 } 1156 1157 /** 1158 * Dump the local log buffer 1159 */ 1160 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1161 pw.println("Dump of WifiConnectivityManager"); 1162 pw.println("WifiConnectivityManager - Log Begin ----"); 1163 pw.println("WifiConnectivityManager - Number of connectivity attempts rate limited: " 1164 + mTotalConnectivityAttemptsRateLimited); 1165 mLocalLog.dump(fd, pw, args); 1166 pw.println("WifiConnectivityManager - Log End ----"); 1167 } 1168 1169 @VisibleForTesting 1170 int getLowRssiNetworkRetryDelay() { 1171 return mPnoScanListener.getLowRssiNetworkRetryDelay(); 1172 } 1173 1174 @VisibleForTesting 1175 long getLastPeriodicSingleScanTimeStamp() { 1176 return mLastPeriodicSingleScanTimeStamp; 1177 } 1178} 1179