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