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