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