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