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