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