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