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