WifiConnectivityManager.java revision 3d09b9117df33608a43c1fbe0135123e2de9aea2
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.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    private static final String TAG = "WifiConnectivityManager";
58
59    // Periodic scan interval in milli-seconds. This is the scan
60    // performed when screen is on.
61    private static final int PERIODIC_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds
62    // PNO scan interval in milli-seconds. This is the scan
63    // performed when screen is off and disconnected.
64    private static final int DISCONNECTED_PNO_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds
65    // PNO scan interval in milli-seconds. This is the scan
66    // performed when screen is off and connected.
67    private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
68    // When a network is found by PNO scan but gets rejected by QNS due to its
69    // low RSSI value, scan will be reschduled in an exponential back off manner.
70    private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds
71    private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds
72    // Maximum number of retries when starting a scan failed
73    private static final int MAX_SCAN_RESTART_ALLOWED = 5;
74    // Number of milli-seconds to delay before retry starting
75    // a previously failed scan
76    private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds
77    // When in disconnected mode, a watchdog timer will be fired
78    // every WATCHDOG_INTERVAL_MS to start a single scan. This is
79    // to prevent caveat from things like PNO scan.
80    private static final int WATCHDOG_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
81    // This is the time interval for the connection attempt rate calculation. Connection attempt
82    // timestamps beyond this interval is evicted from the list.
83    public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins
84    // Max number of connection attempts in the above time interval.
85    public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6;
86
87    // WifiStateMachine has a bunch of states. From the
88    // WifiConnectivityManager's perspective it only cares
89    // if it is in Connected state, Disconnected state or in
90    // transition between these two states.
91    public static final int WIFI_STATE_UNKNOWN = 0;
92    public static final int WIFI_STATE_CONNECTED = 1;
93    public static final int WIFI_STATE_DISCONNECTED = 2;
94    public static final int WIFI_STATE_TRANSITIONING = 3;
95
96    // Due to b/28020168, timer based single scan will be scheduled every
97    // PERIODIC_SCAN_INTERVAL_MS to provide periodic scan.
98    private static final boolean ENABLE_BACKGROUND_SCAN = false;
99    // Flag to turn on connected PNO, when needed
100    private static final boolean ENABLE_CONNECTED_PNO_SCAN = false;
101
102    private final WifiStateMachine mStateMachine;
103    private final WifiScanner mScanner;
104    private final WifiConfigManager mConfigManager;
105    private final WifiInfo mWifiInfo;
106    private final WifiQualifiedNetworkSelector mQualifiedNetworkSelector;
107    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
108    private final AlarmManager mAlarmManager;
109    private final Handler mEventHandler;
110    private final Clock mClock;
111    private final LocalLog mLocalLog =
112            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 1024 : 16384);
113    private final LinkedList<Long> mConnectionAttemptTimeStamps;
114
115    private boolean mDbg = false;
116    private boolean mWifiEnabled = false;
117    private boolean mWifiConnectivityManagerEnabled = true;
118    private boolean mForceSelectNetwork = false;
119    private boolean mScreenOn = false;
120    private int mWifiState = WIFI_STATE_UNKNOWN;
121    private boolean mUntrustedConnectionAllowed = false;
122    private int mScanRestartCount = 0;
123    private int mSingleScanRestartCount = 0;
124    private int mTotalConnectivityAttemptsRateLimited = 0;
125
126    // PNO settings
127    private int mMin5GHzRssi;
128    private int mMin24GHzRssi;
129    private int mInitialScoreMax;
130    private int mCurrentConnectionBonus;
131    private int mSameNetworkBonus;
132    private int mSecureBonus;
133    private int mBand5GHzBonus;
134
135    // A helper to log debugging information in the local log buffer, which can
136    // be retrieved in bugreport.
137    private void localLog(String log) {
138        mLocalLog.log(log);
139    }
140
141    // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
142    // if the start scan command failed. An timer is used here to make it a deferred retry.
143    private final AlarmManager.OnAlarmListener mRestartScanListener =
144            new AlarmManager.OnAlarmListener() {
145                public void onAlarm() {
146                    startConnectivityScan(mForceSelectNetwork);
147                }
148            };
149
150    // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
151    // if the start scan command failed. An timer is used here to make it a deferred retry.
152    private final AlarmManager.OnAlarmListener mRestartSingleScanListener =
153            new AlarmManager.OnAlarmListener() {
154                public void onAlarm() {
155                    startSingleScan();
156                }
157            };
158
159    // As a watchdog mechanism, a single scan will be scheduled every WATCHDOG_INTERVAL_MS
160    // if it is in the WIFI_STATE_DISCONNECTED state.
161    private final AlarmManager.OnAlarmListener mWatchdogListener =
162            new AlarmManager.OnAlarmListener() {
163                public void onAlarm() {
164                    watchdogHandler();
165                }
166            };
167
168    // Due to b/28020168, timer based single scan will be scheduled every
169    // PERIODIC_SCAN_INTERVAL_MS to provide periodic scan.
170    private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener =
171            new AlarmManager.OnAlarmListener() {
172                public void onAlarm() {
173                    periodicScanTimerHandler();
174                }
175            };
176
177    /**
178     * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
179     * Executes selection of potential network candidates, initiation of connection attempt to that
180     * network.
181     *
182     * @return true - if a candidate is selected by QNS
183     *         false - if no candidate is selected by QNS
184     */
185    private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) {
186        localLog(listenerName + " onResults: start QNS");
187        WifiConfiguration candidate =
188                mQualifiedNetworkSelector.selectQualifiedNetwork(mForceSelectNetwork,
189                mUntrustedConnectionAllowed, scanDetails,
190                mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
191                mStateMachine.isDisconnected(),
192                mStateMachine.isSupplicantTransientState());
193        mWifiLastResortWatchdog.updateAvailableNetworks(
194                mQualifiedNetworkSelector.getFilteredScanDetails());
195        if (candidate != null) {
196            localLog(listenerName + ": QNS candidate-" + candidate.SSID);
197            connectToNetwork(candidate);
198            return true;
199        } else {
200            return false;
201        }
202    }
203
204    // Periodic scan results listener. A periodic scan is initiated when
205    // screen is on.
206    private class PeriodicScanListener implements WifiScanner.ScanListener {
207        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
208
209        public void clearScanDetails() {
210            mScanDetails.clear();
211        }
212
213        @Override
214        public void onSuccess() {
215            localLog("PeriodicScanListener onSuccess");
216
217            // reset the count
218            mScanRestartCount = 0;
219        }
220
221        @Override
222        public void onFailure(int reason, String description) {
223            Log.e(TAG, "PeriodicScanListener onFailure:"
224                          + " reason: " + reason
225                          + " description: " + description);
226
227            // reschedule the scan
228            if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
229                scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
230            } else {
231                mScanRestartCount = 0;
232                Log.e(TAG, "Failed to successfully start periodic scan for "
233                          + MAX_SCAN_RESTART_ALLOWED + " times");
234            }
235        }
236
237        @Override
238        public void onPeriodChanged(int periodInMs) {
239            localLog("PeriodicScanListener onPeriodChanged: "
240                          + "actual scan period " + periodInMs + "ms");
241        }
242
243        @Override
244        public void onResults(WifiScanner.ScanData[] results) {
245            handleScanResults(mScanDetails, "PeriodicScanListener");
246            clearScanDetails();
247        }
248
249        @Override
250        public void onFullResult(ScanResult fullScanResult) {
251            if (mDbg) {
252                localLog("PeriodicScanListener onFullResult: "
253                            + fullScanResult.SSID + " capabilities "
254                            + fullScanResult.capabilities);
255            }
256
257            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
258        }
259    }
260
261    private final PeriodicScanListener mPeriodicScanListener = new PeriodicScanListener();
262
263    // Single scan results listener. A single scan is initiated when
264    // Disconnected/ConnectedPNO scan found a valid network and woke up
265    // the system, or by the watchdog timer.
266    private class SingleScanListener implements WifiScanner.ScanListener {
267        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
268
269        public void clearScanDetails() {
270            mScanDetails.clear();
271        }
272
273        @Override
274        public void onSuccess() {
275            localLog("SingleScanListener onSuccess");
276
277            // reset the count
278            mSingleScanRestartCount = 0;
279        }
280
281        @Override
282        public void onFailure(int reason, String description) {
283            Log.e(TAG, "SingleScanListener onFailure:"
284                          + " reason: " + reason
285                          + " description: " + description);
286
287            // reschedule the scan
288            if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
289                scheduleDelayedSingleScan();
290            } else {
291                mSingleScanRestartCount = 0;
292                Log.e(TAG, "Failed to successfully start single scan for "
293                          + MAX_SCAN_RESTART_ALLOWED + " times");
294            }
295        }
296
297        @Override
298        public void onPeriodChanged(int periodInMs) {
299            localLog("SingleScanListener onPeriodChanged: "
300                          + "actual scan period " + periodInMs + "ms");
301        }
302
303        @Override
304        public void onResults(WifiScanner.ScanData[] results) {
305            handleScanResults(mScanDetails, "SingleScanListener");
306            clearScanDetails();
307        }
308
309        @Override
310        public void onFullResult(ScanResult fullScanResult) {
311            if (mDbg) {
312                localLog("SingleScanListener onFullResult: "
313                            + fullScanResult.SSID + " capabilities "
314                            + fullScanResult.capabilities);
315            }
316
317            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
318        }
319    }
320
321    // re-enable this when b/27695292 is fixed
322    // private final SingleScanListener mSingleScanListener = new SingleScanListener();
323
324    // PNO scan results listener for both disconected and connected PNO scanning.
325    // A PNO scan is initiated when screen is off.
326    private class PnoScanListener implements WifiScanner.PnoScanListener {
327        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
328        private int mLowRssiNetworkRetryDelay =
329                LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
330
331        public void clearScanDetails() {
332            mScanDetails.clear();
333        }
334
335        // Reset to the start value when either a non-PNO scan is started or
336        // QNS selects a candidate from the PNO scan results.
337        public void resetLowRssiNetworkRetryDelay() {
338            mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
339        }
340
341        @VisibleForTesting
342        public int getLowRssiNetworkRetryDelay() {
343            return mLowRssiNetworkRetryDelay;
344        }
345
346        @Override
347        public void onSuccess() {
348            localLog("PnoScanListener onSuccess");
349
350            // reset the count
351            mScanRestartCount = 0;
352        }
353
354        @Override
355        public void onFailure(int reason, String description) {
356            Log.e(TAG, "PnoScanListener onFailure:"
357                          + " reason: " + reason
358                          + " description: " + description);
359
360            // reschedule the scan
361            if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
362                scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
363            } else {
364                mScanRestartCount = 0;
365                Log.e(TAG, "Failed to successfully start PNO scan for "
366                          + MAX_SCAN_RESTART_ALLOWED + " times");
367            }
368        }
369
370        @Override
371        public void onPeriodChanged(int periodInMs) {
372            localLog("PnoScanListener onPeriodChanged: "
373                          + "actual scan period " + periodInMs + "ms");
374        }
375
376        // Currently the PNO scan results doesn't include IE,
377        // which contains information required by QNS. Ignore them
378        // for now.
379        @Override
380        public void onResults(WifiScanner.ScanData[] results) {
381        }
382
383        @Override
384        public void onFullResult(ScanResult fullScanResult) {
385        }
386
387        @Override
388        public void onPnoNetworkFound(ScanResult[] results) {
389            localLog("PnoScanListener: onPnoNetworkFound: results len = " + results.length);
390
391            for (ScanResult result: results) {
392                mScanDetails.add(ScanDetailUtil.toScanDetail(result));
393            }
394
395            boolean wasConnectAttempted;
396            wasConnectAttempted = handleScanResults(mScanDetails, "PnoScanListener");
397            clearScanDetails();
398
399            if (!wasConnectAttempted) {
400                // The scan results were rejected by QNS due to low RSSI values
401                if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) {
402                    mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS;
403                }
404                scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelay);
405
406                // Set up the delay value for next retry.
407                mLowRssiNetworkRetryDelay *= 2;
408            } else {
409                resetLowRssiNetworkRetryDelay();
410            }
411        }
412    }
413
414    private final PnoScanListener mPnoScanListener = new PnoScanListener();
415
416    /**
417     * WifiConnectivityManager constructor
418     */
419    public WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
420                WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
421                WifiQualifiedNetworkSelector qualifiedNetworkSelector,
422                WifiInjector wifiInjector, Looper looper) {
423        mStateMachine = stateMachine;
424        mScanner = scanner;
425        mConfigManager = configManager;
426        mWifiInfo = wifiInfo;
427        mQualifiedNetworkSelector = qualifiedNetworkSelector;
428        mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
429        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
430        mEventHandler = new Handler(looper);
431        mClock = wifiInjector.getClock();
432        mConnectionAttemptTimeStamps = new LinkedList<>();
433
434        mMin5GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI;
435        mMin24GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI;
436        mBand5GHzBonus = WifiQualifiedNetworkSelector.BAND_AWARD_5GHz;
437        mCurrentConnectionBonus = mConfigManager.mCurrentNetworkBoost.get();
438        mSameNetworkBonus = context.getResources().getInteger(
439                                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
440        mSecureBonus = context.getResources().getInteger(
441                            R.integer.config_wifi_framework_SECURITY_AWARD);
442        mInitialScoreMax = (mConfigManager.mThresholdSaturatedRssi24.get()
443                            + WifiQualifiedNetworkSelector.RSSI_SCORE_OFFSET)
444                            * WifiQualifiedNetworkSelector.RSSI_SCORE_SLOPE;
445
446        Log.i(TAG, "PNO settings:" + " min5GHzRssi " + mMin5GHzRssi
447                    + " min24GHzRssi " + mMin24GHzRssi
448                    + " currentConnectionBonus " + mCurrentConnectionBonus
449                    + " sameNetworkBonus " + mSameNetworkBonus
450                    + " secureNetworkBonus " + mSecureBonus
451                    + " initialScoreMax " + mInitialScoreMax);
452
453        Log.i(TAG, "ConnectivityScanManager initialized ");
454    }
455
456    /**
457     * This checks the connection attempt rate and recommends whether the connection attempt
458     * should be skipped or not. This attempts to rate limit the rate of connections to
459     * prevent us from flapping between networks and draining battery rapidly.
460     */
461    private boolean shouldSkipConnectionAttempt(Long currentTimeMillis) {
462        Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator();
463        // First evict old entries from the queue.
464        while (attemptIter.hasNext()) {
465            Long connectionAttemptTimeMillis = attemptIter.next();
466            if ((currentTimeMillis - connectionAttemptTimeMillis)
467                    > MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS) {
468                attemptIter.remove();
469            } else {
470                // This list is sorted by timestamps, so we can skip any more checks
471                break;
472            }
473        }
474        // If we've reached the max connection attempt rate, skip this connection attempt
475        return (mConnectionAttemptTimeStamps.size() >= MAX_CONNECTION_ATTEMPTS_RATE);
476    }
477
478    /**
479     * Add the current connection attempt timestamp to our queue of connection attempts.
480     */
481    private void noteConnectionAttempt(Long currentTimeMillis) {
482        mConnectionAttemptTimeStamps.addLast(currentTimeMillis);
483    }
484
485    /**
486     * This is used to clear the connection attempt rate limiter. This is done when the user
487     * explicitly tries to connect to a specified network.
488     */
489    private void clearConnectionAttemptTimeStamps() {
490        mConnectionAttemptTimeStamps.clear();
491    }
492
493    /**
494     * Attempt to connect to a network candidate.
495     *
496     * Based on the currently connected network, this menthod determines whether we should
497     * connect or roam to the network candidate recommended by QNS.
498     */
499    private void connectToNetwork(WifiConfiguration candidate) {
500        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
501        if (scanResultCandidate == null) {
502            Log.e(TAG, "connectToNetwork: bad candidate - "  + candidate
503                    + " scanResult: " + scanResultCandidate);
504            return;
505        }
506
507        String targetBssid = scanResultCandidate.BSSID;
508        String targetAssociationId = candidate.SSID + " : " + targetBssid;
509        if (targetBssid != null && targetBssid.equals(mWifiInfo.getBSSID())
510                    && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
511            localLog("connectToNetwork: Either already connected "
512                    + "or is connecting to " + targetAssociationId);
513            return;
514        }
515
516        Long currentTimeMillis = mClock.currentTimeMillis();
517        if (!mScreenOn && shouldSkipConnectionAttempt(currentTimeMillis)) {
518            localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
519            mTotalConnectivityAttemptsRateLimited++;
520            return;
521        }
522        noteConnectionAttempt(currentTimeMillis);
523
524        WifiConfiguration currentConnectedNetwork = mConfigManager
525                .getWifiConfiguration(mWifiInfo.getNetworkId());
526        String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
527                (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());
528
529        if (currentConnectedNetwork != null
530                && (currentConnectedNetwork.networkId == candidate.networkId
531                || currentConnectedNetwork.isLinked(candidate))) {
532            localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
533                        + targetAssociationId);
534            mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
535        } else {
536            localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
537                        + targetAssociationId);
538            mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
539        }
540    }
541
542    // Helper for selecting the band for connectivity scan
543    private int getScanBand() {
544        int freqBand = mStateMachine.getFrequencyBand();
545        if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_5GHZ) {
546            return WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS;
547        } else if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ) {
548            return WifiScanner.WIFI_BAND_24_GHZ;
549        } else {
550            return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
551        }
552    }
553
554    // Watchdog timer handler
555    private void watchdogHandler() {
556        localLog("watchdogHandler");
557
558        // Schedule the next timer and start a single scan if we are in disconnected state.
559        // Otherwise, the watchdog timer will be scheduled when entering disconnected
560        // state.
561        if (mWifiState == WIFI_STATE_DISCONNECTED) {
562            Log.i(TAG, "start a single scan from watchdogHandler");
563
564            scheduleWatchdogTimer();
565            startSingleScan();
566        }
567    }
568
569    // Periodic scan timer handler
570    private void periodicScanTimerHandler() {
571        localLog("periodicScanTimerHandler");
572
573        // Schedule the next timer and start a single scan if screen is on.
574        if (mScreenOn) {
575            schedulePeriodicScanTimer();
576            startSingleScan();
577        }
578    }
579
580    // Start a single scan
581    private void startSingleScan() {
582        if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
583            return;
584        }
585
586        mPnoScanListener.resetLowRssiNetworkRetryDelay();
587
588        ScanSettings settings = new ScanSettings();
589        settings.band = getScanBand();
590        settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
591                            | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
592        settings.numBssidsPerScan = 0;
593
594        //Retrieve the list of hidden networkId's to scan for.
595        Set<Integer> hiddenNetworkIds = mConfigManager.getHiddenConfiguredNetworkIds();
596        if (hiddenNetworkIds != null && hiddenNetworkIds.size() > 0) {
597            int i = 0;
598            settings.hiddenNetworkIds = new int[hiddenNetworkIds.size()];
599            for (Integer netId : hiddenNetworkIds) {
600                settings.hiddenNetworkIds[i++] = netId;
601            }
602        }
603
604        // re-enable this when b/27695292 is fixed
605        // mSingleScanListener.clearScanDetails();
606        // mScanner.startScan(settings, mSingleScanListener, WIFI_WORK_SOURCE);
607        SingleScanListener singleScanListener = new SingleScanListener();
608        mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
609    }
610
611    // Start a periodic scan when screen is on
612    private void startPeriodicScan() {
613        mPnoScanListener.resetLowRssiNetworkRetryDelay();
614
615        // Due to b/28020168, timer based single scan will be scheduled every
616        // PERIODIC_SCAN_INTERVAL_MS to provide periodic scan.
617        if (!ENABLE_BACKGROUND_SCAN) {
618            startSingleScan();
619            schedulePeriodicScanTimer();
620        } else {
621            ScanSettings settings = new ScanSettings();
622            settings.band = getScanBand();
623            settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
624                                | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
625            settings.numBssidsPerScan = 0;
626            settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS;
627
628            mPeriodicScanListener.clearScanDetails();
629            mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE);
630        }
631    }
632
633    // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected
634    private void startDisconnectedPnoScan() {
635        // Initialize PNO settings
636        PnoSettings pnoSettings = new PnoSettings();
637        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList =
638                mConfigManager.retrieveDisconnectedPnoNetworkList();
639        int listSize = pnoNetworkList.size();
640
641        if (listSize == 0) {
642            // No saved network
643            localLog("No saved network for starting disconnected PNO.");
644            return;
645        }
646
647        pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize];
648        pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList);
649        pnoSettings.min5GHzRssi = mMin5GHzRssi;
650        pnoSettings.min24GHzRssi = mMin24GHzRssi;
651        pnoSettings.initialScoreMax = mInitialScoreMax;
652        pnoSettings.currentConnectionBonus = mCurrentConnectionBonus;
653        pnoSettings.sameNetworkBonus = mSameNetworkBonus;
654        pnoSettings.secureBonus = mSecureBonus;
655        pnoSettings.band5GHzBonus = mBand5GHzBonus;
656
657        // Initialize scan settings
658        ScanSettings scanSettings = new ScanSettings();
659        scanSettings.band = getScanBand();
660        scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
661        scanSettings.numBssidsPerScan = 0;
662        scanSettings.periodInMs = DISCONNECTED_PNO_SCAN_INTERVAL_MS;
663        // TODO: enable exponential back off scan later to further save energy
664        // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs;
665
666        mPnoScanListener.clearScanDetails();
667
668        mScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
669    }
670
671    // Start a ConnectedPNO scan when screen is off and Wifi is connected
672    private void startConnectedPnoScan() {
673        // Disable ConnectedPNO for now due to b/28020168
674        if (!ENABLE_CONNECTED_PNO_SCAN) {
675            return;
676        }
677
678        // Initialize PNO settings
679        PnoSettings pnoSettings = new PnoSettings();
680        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList =
681                mConfigManager.retrieveConnectedPnoNetworkList();
682        int listSize = pnoNetworkList.size();
683
684        if (listSize == 0) {
685            // No saved network
686            localLog("No saved network for starting connected PNO.");
687            return;
688        }
689
690        pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize];
691        pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList);
692        pnoSettings.min5GHzRssi = mMin5GHzRssi;
693        pnoSettings.min24GHzRssi = mMin24GHzRssi;
694        pnoSettings.initialScoreMax = mInitialScoreMax;
695        pnoSettings.currentConnectionBonus = mCurrentConnectionBonus;
696        pnoSettings.sameNetworkBonus = mSameNetworkBonus;
697        pnoSettings.secureBonus = mSecureBonus;
698        pnoSettings.band5GHzBonus = mBand5GHzBonus;
699
700        // Initialize scan settings
701        ScanSettings scanSettings = new ScanSettings();
702        scanSettings.band = getScanBand();
703        scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
704        scanSettings.numBssidsPerScan = 0;
705        scanSettings.periodInMs = CONNECTED_PNO_SCAN_INTERVAL_MS;
706        // TODO: enable exponential back off scan later to further save energy
707        // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs;
708
709        mPnoScanListener.clearScanDetails();
710
711        mScanner.startConnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
712    }
713
714    // Set up watchdog timer
715    private void scheduleWatchdogTimer() {
716        Log.i(TAG, "scheduleWatchdogTimer");
717
718        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
719                            mClock.currentTimeMillis() + WATCHDOG_INTERVAL_MS,
720                            "WifiConnectivityManager Schedule Watchdog Timer",
721                            mWatchdogListener, mEventHandler);
722    }
723
724    // Set up periodic scan timer
725    private void schedulePeriodicScanTimer() {
726        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
727                            mClock.currentTimeMillis() + PERIODIC_SCAN_INTERVAL_MS,
728                            "WifiConnectivityManager Schedule Periodic Scan Timer",
729                            mPeriodicScanTimerListener, mEventHandler);
730    }
731
732    // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS
733    private void scheduleDelayedSingleScan() {
734        localLog("scheduleDelayedSingleScan");
735
736        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
737                            mClock.currentTimeMillis() + RESTART_SCAN_DELAY_MS,
738                            "WifiConnectivityManager Restart Single Scan",
739                            mRestartSingleScanListener, mEventHandler);
740    }
741
742    // Set up timer to start a delayed scan after msFromNow milli-seconds
743    private void scheduleDelayedConnectivityScan(int msFromNow) {
744        localLog("scheduleDelayedConnectivityScan");
745
746        mAlarmManager.set(AlarmManager.RTC_WAKEUP,
747                            mClock.currentTimeMillis() + msFromNow,
748                            "WifiConnectivityManager Restart Scan",
749                            mRestartScanListener, mEventHandler);
750
751    }
752
753    // Start a connectivity scan. The scan method is chosen according to
754    // the current screen state and WiFi state.
755    private void startConnectivityScan(boolean forceSelectNetwork) {
756        localLog("startConnectivityScan: screenOn=" + mScreenOn
757                        + " wifiState=" + mWifiState
758                        + " forceSelectNetwork=" + forceSelectNetwork
759                        + " wifiEnabled=" + mWifiEnabled
760                        + " wifiConnectivityManagerEnabled="
761                        + mWifiConnectivityManagerEnabled);
762
763        if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
764            return;
765        }
766
767        // Always stop outstanding connecivity scan if there is any
768        stopConnectivityScan();
769
770        // Don't start a connectivity scan while Wifi is in the transition
771        // between connected and disconnected states.
772        if (mWifiState != WIFI_STATE_CONNECTED && mWifiState != WIFI_STATE_DISCONNECTED) {
773            return;
774        }
775
776        mForceSelectNetwork = forceSelectNetwork;
777
778        if (mScreenOn) {
779            startPeriodicScan();
780        } else { // screenOff
781            if (mWifiState == WIFI_STATE_CONNECTED) {
782                startConnectedPnoScan();
783            } else {
784                startDisconnectedPnoScan();
785            }
786        }
787    }
788
789    // Stop connectivity scan if there is any.
790    private void stopConnectivityScan() {
791        // Due to b/28020168, timer based single scan will be scheduled every
792        // PERIODIC_SCAN_INTERVAL_MS to provide periodic scan.
793        if (!ENABLE_BACKGROUND_SCAN) {
794            mAlarmManager.cancel(mPeriodicScanTimerListener);
795        } else {
796            mScanner.stopBackgroundScan(mPeriodicScanListener);
797        }
798        mScanner.stopPnoScan(mPnoScanListener);
799        mScanRestartCount = 0;
800    }
801
802    /**
803     * Handler for screen state (on/off) changes
804     */
805    public void handleScreenStateChanged(boolean screenOn) {
806        localLog("handleScreenStateChanged: screenOn=" + screenOn);
807
808        mScreenOn = screenOn;
809
810        startConnectivityScan(false);
811    }
812
813    /**
814     * Handler for WiFi state (connected/disconnected) changes
815     */
816    public void handleConnectionStateChanged(int state) {
817        localLog("handleConnectionStateChanged: state=" + state);
818
819        mWifiState = state;
820
821        // Kick off the watchdog timer if entering disconnected state
822        if (mWifiState == WIFI_STATE_DISCONNECTED) {
823            scheduleWatchdogTimer();
824        }
825
826        startConnectivityScan(false);
827    }
828
829    /**
830     * Handler when user toggles whether untrusted connection is allowed
831     */
832    public void setUntrustedConnectionAllowed(boolean allowed) {
833        Log.i(TAG, "setUntrustedConnectionAllowed: allowed=" + allowed);
834
835        if (mUntrustedConnectionAllowed != allowed) {
836            mUntrustedConnectionAllowed = allowed;
837            startConnectivityScan(false);
838        }
839    }
840
841    /**
842     * Handler when user specifies a particular network to connect to
843     */
844    public void connectToUserSelectNetwork(int netId, boolean persistent) {
845        Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId
846                   + " persist=" + persistent);
847
848        mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);
849
850        clearConnectionAttemptTimeStamps();
851
852        // Initiate a scan which will trigger the connection to the user selected
853        // network when scan result is available.
854        startConnectivityScan(true);
855    }
856
857    /**
858     * Handler for on-demand connectivity scan
859     */
860    public void forceConnectivityScan() {
861        Log.i(TAG, "forceConnectivityScan");
862
863        startConnectivityScan(false);
864    }
865
866    /**
867     * Track whether a BSSID should be enabled or disabled for QNS
868     */
869    public boolean trackBssid(String bssid, boolean enable) {
870        Log.i(TAG, "trackBssid: " + (enable ? "enable " : "disable ") + bssid);
871
872        boolean ret = mQualifiedNetworkSelector
873                            .enableBssidForQualityNetworkSelection(bssid, enable);
874
875        if (ret && !enable) {
876            // Disabling a BSSID can happen when the AP candidate to connect to has
877            // no capacity for new stations. We start another scan immediately so that QNS
878            // can give us another candidate to connect to.
879            startConnectivityScan(false);
880        }
881
882        return ret;
883    }
884
885    /**
886     * Set band preference when doing scan and making connection
887     */
888    public void setUserPreferredBand(int band) {
889        Log.i(TAG, "User band preference: " + band);
890
891        mQualifiedNetworkSelector.setUserPreferredBand(band);
892        startConnectivityScan(false);
893    }
894
895    /**
896     * Inform WiFi is enabled for connection or not
897     */
898    public void setWifiEnabled(boolean enable) {
899        Log.i(TAG, "Set WiFi " + (enable ? "enabled" : "disabled"));
900
901        mWifiEnabled = enable;
902
903        if (!mWifiEnabled) {
904            stopConnectivityScan();
905        }
906    }
907
908    /**
909     * Turn on/off the WifiConnectivityMangager at runtime
910     */
911    public void enable(boolean enable) {
912        Log.i(TAG, "Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled"));
913
914        mWifiConnectivityManagerEnabled = enable;
915
916        if (!mWifiConnectivityManagerEnabled) {
917            stopConnectivityScan();
918        }
919    }
920
921    /**
922     * Enable/disable verbose logging
923     */
924    public void enableVerboseLogging(int verbose) {
925        mDbg = verbose > 0;
926    }
927
928    /**
929     * Dump the local log buffer
930     */
931    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
932        pw.println("Dump of WifiConnectivityManager");
933        pw.println("WifiConnectivityManager - Log Begin ----");
934        pw.println("WifiConnectivityManager - Number of connectivity attempts rate limited: "
935                + mTotalConnectivityAttemptsRateLimited);
936        mLocalLog.dump(fd, pw, args);
937        pw.println("WifiConnectivityManager - Log End ----");
938    }
939
940    @VisibleForTesting
941    int getLowRssiNetworkRetryDelay() {
942        return mPnoScanListener.getLowRssiNetworkRetryDelay();
943    }
944}
945