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