WifiConnectivityManager.java revision f2a46f11dd92f2820e96b1b8b69b433012d6bcef
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi;
18
19import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE;
20
21import android.app.AlarmManager;
22import android.content.Context;
23import android.net.wifi.ScanResult;
24import android.net.wifi.SupplicantState;
25import android.net.wifi.WifiConfiguration;
26import android.net.wifi.WifiInfo;
27import android.net.wifi.WifiScanner;
28import android.net.wifi.WifiScanner.PnoSettings;
29import android.net.wifi.WifiScanner.ScanSettings;
30import android.os.Handler;
31import android.os.Looper;
32import android.util.LocalLog;
33import android.util.Log;
34
35import com.android.internal.R;
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.server.wifi.util.ScanResultUtil;
38
39import java.util.ArrayList;
40import java.util.Iterator;
41import java.util.LinkedList;
42import java.util.List;
43import java.util.Set;
44
45/**
46 * This class manages all the connectivity related scanning activities.
47 *
48 * When the screen is turned on or off, WiFi is connected or disconnected,
49 * or on-demand, a scan is initiatiated and the scan results are passed
50 * to WifiNetworkSelector for it to make a recommendation on which network
51 * to connect to.
52 */
53public class WifiConnectivityManager {
54    public static final String WATCHDOG_TIMER_TAG =
55            "WifiConnectivityManager Schedule Watchdog Timer";
56    public static final String PERIODIC_SCAN_TIMER_TAG =
57            "WifiConnectivityManager Schedule Periodic Scan Timer";
58    public static final String RESTART_SINGLE_SCAN_TIMER_TAG =
59            "WifiConnectivityManager Restart Single Scan";
60    public static final String RESTART_CONNECTIVITY_SCAN_TIMER_TAG =
61            "WifiConnectivityManager Restart Scan";
62
63    private static final String TAG = "WifiConnectivityManager";
64    private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
65    // Constants to indicate whether a scan should start immediately or
66    // it should comply to the minimum scan interval rule.
67    private static final boolean SCAN_IMMEDIATELY = true;
68    private static final boolean SCAN_ON_SCHEDULE = false;
69    // Periodic scan interval in milli-seconds. This is the scan
70    // performed when screen is on.
71    @VisibleForTesting
72    public static final int PERIODIC_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds
73    // When screen is on and WiFi traffic is heavy, exponential backoff
74    // connectivity scans are scheduled. This constant defines the maximum
75    // scan interval in this scenario.
76    @VisibleForTesting
77    public static final int MAX_PERIODIC_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
78    // PNO scan interval in milli-seconds. This is the scan
79    // performed when screen is off and disconnected.
80    private static final int DISCONNECTED_PNO_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds
81    // PNO scan interval in milli-seconds. This is the scan
82    // performed when screen is off and connected.
83    private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
84    // When a network is found by PNO scan but gets rejected by Wifi Network Selector due
85    // to its low RSSI value, scan will be reschduled in an exponential back off manner.
86    private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds
87    private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds
88    // Maximum number of retries when starting a scan failed
89    @VisibleForTesting
90    public static final int MAX_SCAN_RESTART_ALLOWED = 5;
91    // Number of milli-seconds to delay before retry starting
92    // a previously failed scan
93    private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds
94    // When in disconnected mode, a watchdog timer will be fired
95    // every WATCHDOG_INTERVAL_MS to start a single scan. This is
96    // to prevent caveat from things like PNO scan.
97    private static final int WATCHDOG_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes
98    // Restricted channel list age out value.
99    private static final int CHANNEL_LIST_AGE_MS = 60 * 60 * 1000; // 1 hour
100    // This is the time interval for the connection attempt rate calculation. Connection attempt
101    // timestamps beyond this interval is evicted from the list.
102    public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins
103    // Max number of connection attempts in the above time interval.
104    public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6;
105    // Packet tx/rx rates to determine if we want to do partial vs full scans.
106    // TODO(b/31180330): Make these device configs.
107    public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8;
108    public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16;
109
110    // WifiStateMachine has a bunch of states. From the
111    // WifiConnectivityManager's perspective it only cares
112    // if it is in Connected state, Disconnected state or in
113    // transition between these two states.
114    public static final int WIFI_STATE_UNKNOWN = 0;
115    public static final int WIFI_STATE_CONNECTED = 1;
116    public static final int WIFI_STATE_DISCONNECTED = 2;
117    public static final int WIFI_STATE_TRANSITIONING = 3;
118
119    // Saved network evaluator priority
120    private static final int SAVED_NETWORK_EVALUATOR_PRIORITY = 1;
121    private static final int EXTERNAL_SCORE_EVALUATOR_PRIORITY = 2;
122
123    private final WifiStateMachine mStateMachine;
124    private final WifiScanner mScanner;
125    private final WifiConfigManager mConfigManager;
126    private final WifiInfo mWifiInfo;
127    private final WifiNetworkSelector mNetworkSelector;
128    private final WifiLastResortWatchdog mWifiLastResortWatchdog;
129    private final WifiMetrics mWifiMetrics;
130    private final AlarmManager mAlarmManager;
131    private final Handler mEventHandler;
132    private final Clock mClock;
133    private final LocalLog mLocalLog;
134    private final LinkedList<Long> mConnectionAttemptTimeStamps;
135
136    private boolean mDbg = false;
137    private boolean mWifiEnabled = false;
138    private boolean mWifiConnectivityManagerEnabled = true;
139    private boolean mScreenOn = false;
140    private int mWifiState = WIFI_STATE_UNKNOWN;
141    private boolean mUntrustedConnectionAllowed = false;
142    private int mScanRestartCount = 0;
143    private int mSingleScanRestartCount = 0;
144    private int mTotalConnectivityAttemptsRateLimited = 0;
145    private String mLastConnectionAttemptBssid = null;
146    private int mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
147    private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
148    private boolean mPnoScanStarted = false;
149    private boolean mPeriodicScanTimerSet = false;
150    // Device configs
151    private boolean mEnableAutoJoinWhenAssociated;
152    // PNO settings
153    private int mMin5GHzRssi;
154    private int mMin24GHzRssi;
155    private int mInitialScoreMax;
156    private int mCurrentConnectionBonus;
157    private int mSameNetworkBonus;
158    private int mSecureBonus;
159    private int mBand5GHzBonus;
160
161    // A helper to log debugging information in the local log buffer, which can
162    // be retrieved in bugreport.
163    private void localLog(String log) {
164        if (mLocalLog != null) {
165            mLocalLog.log(log);
166        }
167    }
168
169    // A periodic/PNO scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
170    // if the start scan command failed. An timer is used here to make it a deferred retry.
171    private final AlarmManager.OnAlarmListener mRestartScanListener =
172            new AlarmManager.OnAlarmListener() {
173                public void onAlarm() {
174                    startConnectivityScan(SCAN_IMMEDIATELY);
175                }
176            };
177
178    // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
179    // if the start scan command failed. An timer is used here to make it a deferred retry.
180    private class RestartSingleScanListener implements AlarmManager.OnAlarmListener {
181        private final boolean mIsFullBandScan;
182
183        RestartSingleScanListener(boolean isFullBandScan) {
184            mIsFullBandScan = isFullBandScan;
185        }
186
187        @Override
188        public void onAlarm() {
189            startSingleScan(mIsFullBandScan);
190        }
191    }
192
193    // As a watchdog mechanism, a single scan will be scheduled every WATCHDOG_INTERVAL_MS
194    // if it is in the WIFI_STATE_DISCONNECTED state.
195    private final AlarmManager.OnAlarmListener mWatchdogListener =
196            new AlarmManager.OnAlarmListener() {
197                public void onAlarm() {
198                    watchdogHandler();
199                }
200            };
201
202    // Due to b/28020168, timer based single scan will be scheduled
203    // to provide periodic scan in an exponential backoff fashion.
204    private final AlarmManager.OnAlarmListener mPeriodicScanTimerListener =
205            new AlarmManager.OnAlarmListener() {
206                public void onAlarm() {
207                    periodicScanTimerHandler();
208                }
209            };
210
211    /**
212     * Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
213     * Executes selection of potential network candidates, initiation of connection attempt to that
214     * network.
215     *
216     * @return true - if a candidate is selected by WifiNetworkSelector
217     *         false - if no candidate is selected by WifiNetworkSelector
218     */
219    private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) {
220        if (mStateMachine.isLinkDebouncing() || mStateMachine.isSupplicantTransientState()) {
221            localLog(listenerName + " onResults: No network selection because linkDebouncing is "
222                    + mStateMachine.isLinkDebouncing() + " and supplicantTransient is "
223                    + mStateMachine.isSupplicantTransientState());
224            return false;
225        }
226
227        localLog(listenerName + " onResults: start network selection");
228
229        WifiConfiguration candidate =
230                mNetworkSelector.selectNetwork(scanDetails,
231                mStateMachine.isConnected(), mStateMachine.isDisconnected(),
232                mUntrustedConnectionAllowed);
233        mWifiLastResortWatchdog.updateAvailableNetworks(
234                mNetworkSelector.getFilteredScanDetails());
235        mWifiMetrics.countScanResults(scanDetails);
236        if (candidate != null) {
237            localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
238            connectToNetwork(candidate);
239            return true;
240        } else {
241            return false;
242        }
243    }
244
245    // All single scan results listener.
246    //
247    // Note: This is the listener for all the available single scan results,
248    //       including the ones initiated by WifiConnectivityManager and
249    //       other modules.
250    private class AllSingleScanListener implements WifiScanner.ScanListener {
251        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
252
253        public void clearScanDetails() {
254            mScanDetails.clear();
255        }
256
257        @Override
258        public void onSuccess() {
259            localLog("registerScanListener onSuccess");
260        }
261
262        @Override
263        public void onFailure(int reason, String description) {
264            Log.e(TAG, "registerScanListener onFailure:"
265                          + " reason: " + reason
266                          + " description: " + description);
267        }
268
269        @Override
270        public void onPeriodChanged(int periodInMs) {
271        }
272
273        @Override
274        public void onResults(WifiScanner.ScanData[] results) {
275            if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
276                clearScanDetails();
277                return;
278            }
279
280            boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener");
281            clearScanDetails();
282
283            // Update metrics to see if a single scan detected a valid network
284            // while PNO scan didn't.
285            // Note: We don't update the background scan metrics any more as it is
286            //       not in use.
287            if (mPnoScanStarted) {
288                if (wasConnectAttempted) {
289                    mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
290                } else {
291                    mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
292                }
293            }
294        }
295
296        @Override
297        public void onFullResult(ScanResult fullScanResult) {
298            if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
299                return;
300            }
301
302            if (mDbg) {
303                localLog("AllSingleScanListener onFullResult: "
304                            + fullScanResult.SSID + " capabilities "
305                            + fullScanResult.capabilities);
306            }
307
308            mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
309        }
310    }
311
312    private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener();
313
314    // Single scan results listener. A single scan is initiated when
315    // DisconnectedPNO scan found a valid network and woke up
316    // the system, or by the watchdog timer, or to form the timer based
317    // periodic scan.
318    //
319    // Note: This is the listener for the single scans initiated by the
320    //        WifiConnectivityManager.
321    private class SingleScanListener implements WifiScanner.ScanListener {
322        private final boolean mIsFullBandScan;
323
324        SingleScanListener(boolean isFullBandScan) {
325            mIsFullBandScan = isFullBandScan;
326        }
327
328        @Override
329        public void onSuccess() {
330            localLog("SingleScanListener onSuccess");
331        }
332
333        @Override
334        public void onFailure(int reason, String description) {
335            Log.e(TAG, "SingleScanListener onFailure:"
336                          + " reason: " + reason
337                          + " description: " + description);
338
339            // reschedule the scan
340            if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
341                scheduleDelayedSingleScan(mIsFullBandScan);
342            } else {
343                mSingleScanRestartCount = 0;
344                Log.e(TAG, "Failed to successfully start single scan for "
345                          + MAX_SCAN_RESTART_ALLOWED + " times");
346            }
347        }
348
349        @Override
350        public void onPeriodChanged(int periodInMs) {
351            localLog("SingleScanListener onPeriodChanged: "
352                          + "actual scan period " + periodInMs + "ms");
353        }
354
355        @Override
356        public void onResults(WifiScanner.ScanData[] results) {
357        }
358
359        @Override
360        public void onFullResult(ScanResult fullScanResult) {
361        }
362    }
363
364    // PNO scan results listener for both disconected and connected PNO scanning.
365    // A PNO scan is initiated when screen is off.
366    private class PnoScanListener implements WifiScanner.PnoScanListener {
367        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
368        private int mLowRssiNetworkRetryDelay =
369                LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
370
371        public void clearScanDetails() {
372            mScanDetails.clear();
373        }
374
375        // Reset to the start value when either a non-PNO scan is started or
376        // WifiNetworkSelector selects a candidate from the PNO scan results.
377        public void resetLowRssiNetworkRetryDelay() {
378            mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
379        }
380
381        @VisibleForTesting
382        public int getLowRssiNetworkRetryDelay() {
383            return mLowRssiNetworkRetryDelay;
384        }
385
386        @Override
387        public void onSuccess() {
388            localLog("PnoScanListener onSuccess");
389        }
390
391        @Override
392        public void onFailure(int reason, String description) {
393            Log.e(TAG, "PnoScanListener onFailure:"
394                          + " reason: " + reason
395                          + " description: " + description);
396
397            // reschedule the scan
398            if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
399                scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
400            } else {
401                mScanRestartCount = 0;
402                Log.e(TAG, "Failed to successfully start PNO scan for "
403                          + MAX_SCAN_RESTART_ALLOWED + " times");
404            }
405        }
406
407        @Override
408        public void onPeriodChanged(int periodInMs) {
409            localLog("PnoScanListener onPeriodChanged: "
410                          + "actual scan period " + periodInMs + "ms");
411        }
412
413        // Currently the PNO scan results doesn't include IE,
414        // which contains information required by WifiNetworkSelector. Ignore them
415        // for now.
416        @Override
417        public void onResults(WifiScanner.ScanData[] results) {
418        }
419
420        @Override
421        public void onFullResult(ScanResult fullScanResult) {
422        }
423
424        @Override
425        public void onPnoNetworkFound(ScanResult[] results) {
426            localLog("PnoScanListener: onPnoNetworkFound: results len = " + results.length);
427
428            for (ScanResult result: results) {
429                mScanDetails.add(ScanResultUtil.toScanDetail(result));
430            }
431
432            boolean wasConnectAttempted;
433            wasConnectAttempted = handleScanResults(mScanDetails, "PnoScanListener");
434            clearScanDetails();
435            mScanRestartCount = 0;
436
437            if (!wasConnectAttempted) {
438                // The scan results were rejected by WifiNetworkSelector due to low RSSI values
439                if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) {
440                    mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS;
441                }
442                scheduleDelayedConnectivityScan(mLowRssiNetworkRetryDelay);
443
444                // Set up the delay value for next retry.
445                mLowRssiNetworkRetryDelay *= 2;
446            } else {
447                resetLowRssiNetworkRetryDelay();
448            }
449        }
450    }
451
452    private final PnoScanListener mPnoScanListener = new PnoScanListener();
453
454    /**
455     * WifiConnectivityManager constructor
456     */
457    WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
458                WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
459                WifiNetworkSelector networkSelector, WifiInjector wifiInjector, Looper looper,
460                boolean enable) {
461        mStateMachine = stateMachine;
462        mScanner = scanner;
463        mConfigManager = configManager;
464        mWifiInfo = wifiInfo;
465        mNetworkSelector = networkSelector;
466        mLocalLog = networkSelector.getLocalLog();
467        mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
468        mWifiMetrics = wifiInjector.getWifiMetrics();
469        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
470        mEventHandler = new Handler(looper);
471        mClock = wifiInjector.getClock();
472        mConnectionAttemptTimeStamps = new LinkedList<>();
473
474        mMin5GHzRssi = context.getResources().getInteger(
475                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
476        mMin24GHzRssi = context.getResources().getInteger(
477                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
478        mBand5GHzBonus = context.getResources().getInteger(
479                R.integer.config_wifi_framework_5GHz_preference_boost_factor);
480        mCurrentConnectionBonus = context.getResources().getInteger(
481                R.integer.config_wifi_framework_current_network_boost);
482        mSameNetworkBonus = context.getResources().getInteger(
483                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
484        mSecureBonus = context.getResources().getInteger(
485                R.integer.config_wifi_framework_SECURITY_AWARD);
486        int thresholdSaturatedRssi24 = context.getResources().getInteger(
487                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
488        mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
489                R.bool.config_wifi_framework_enable_associated_network_selection);
490        mInitialScoreMax = (context.getResources().getInteger(
491                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz)
492                    + context.getResources().getInteger(
493                            R.integer.config_wifi_framework_RSSI_SCORE_OFFSET))
494                * context.getResources().getInteger(
495                        R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
496
497        Log.i(TAG, "PNO settings:" + " min5GHzRssi " + mMin5GHzRssi
498                    + " min24GHzRssi " + mMin24GHzRssi
499                    + " currentConnectionBonus " + mCurrentConnectionBonus
500                    + " sameNetworkBonus " + mSameNetworkBonus
501                    + " secureNetworkBonus " + mSecureBonus
502                    + " initialScoreMax " + mInitialScoreMax);
503
504        // Register the network evaluators
505        SavedNetworkEvaluator savedNetworkEvaluator = new SavedNetworkEvaluator(context,
506                mConfigManager, mClock, mLocalLog);
507        mNetworkSelector.registerNetworkEvaluator(savedNetworkEvaluator,
508                    SAVED_NETWORK_EVALUATOR_PRIORITY);
509
510        ExternalScoreEvaluator externalScoreEvaluator = new ExternalScoreEvaluator(context,
511                mConfigManager, mClock, mLocalLog);
512        mNetworkSelector.registerNetworkEvaluator(externalScoreEvaluator,
513                    EXTERNAL_SCORE_EVALUATOR_PRIORITY);
514
515        // Register for all single scan results
516        mScanner.registerScanListener(mAllSingleScanListener);
517
518        mWifiConnectivityManagerEnabled = enable;
519
520        Log.i(TAG, "ConnectivityScanManager initialized and "
521                + (enable ? "enabled" : "disabled"));
522    }
523
524    /**
525     * This checks the connection attempt rate and recommends whether the connection attempt
526     * should be skipped or not. This attempts to rate limit the rate of connections to
527     * prevent us from flapping between networks and draining battery rapidly.
528     */
529    private boolean shouldSkipConnectionAttempt(Long timeMillis) {
530        Iterator<Long> attemptIter = mConnectionAttemptTimeStamps.iterator();
531        // First evict old entries from the queue.
532        while (attemptIter.hasNext()) {
533            Long connectionAttemptTimeMillis = attemptIter.next();
534            if ((timeMillis - connectionAttemptTimeMillis)
535                    > MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS) {
536                attemptIter.remove();
537            } else {
538                // This list is sorted by timestamps, so we can skip any more checks
539                break;
540            }
541        }
542        // If we've reached the max connection attempt rate, skip this connection attempt
543        return (mConnectionAttemptTimeStamps.size() >= MAX_CONNECTION_ATTEMPTS_RATE);
544    }
545
546    /**
547     * Add the current connection attempt timestamp to our queue of connection attempts.
548     */
549    private void noteConnectionAttempt(Long timeMillis) {
550        mConnectionAttemptTimeStamps.addLast(timeMillis);
551    }
552
553    /**
554     * This is used to clear the connection attempt rate limiter. This is done when the user
555     * explicitly tries to connect to a specified network.
556     */
557    private void clearConnectionAttemptTimeStamps() {
558        mConnectionAttemptTimeStamps.clear();
559    }
560
561    /**
562     * Attempt to connect to a network candidate.
563     *
564     * Based on the currently connected network, this menthod determines whether we should
565     * connect or roam to the network candidate recommended by WifiNetworkSelector.
566     */
567    private void connectToNetwork(WifiConfiguration candidate) {
568        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
569        if (scanResultCandidate == null) {
570            Log.e(TAG, "connectToNetwork: bad candidate - "  + candidate
571                    + " scanResult: " + scanResultCandidate);
572            return;
573        }
574
575        String targetBssid = scanResultCandidate.BSSID;
576        String targetAssociationId = candidate.SSID + " : " + targetBssid;
577
578        // Check if we are already connected or in the process of connecting to the target
579        // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
580        // in case the firmware automatically roamed to a BSSID different from what
581        // WifiNetworkSelector selected.
582        if (targetBssid != null
583                && (targetBssid.equals(mLastConnectionAttemptBssid)
584                    || targetBssid.equals(mWifiInfo.getBSSID()))
585                && SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
586            localLog("connectToNetwork: Either already connected "
587                    + "or is connecting to " + targetAssociationId);
588            return;
589        }
590
591        Long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
592        if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
593            localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
594            mTotalConnectivityAttemptsRateLimited++;
595            return;
596        }
597        noteConnectionAttempt(elapsedTimeMillis);
598
599        mLastConnectionAttemptBssid = targetBssid;
600
601        WifiConfiguration currentConnectedNetwork = mConfigManager
602                .getConfiguredNetwork(mWifiInfo.getNetworkId());
603        String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
604                (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());
605
606        if (currentConnectedNetwork != null
607                && (currentConnectedNetwork.networkId == candidate.networkId
608                || currentConnectedNetwork.isLinked(candidate))) {
609            localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
610                        + targetAssociationId);
611            mStateMachine.startRoamToNetwork(candidate.networkId, scanResultCandidate);
612        } else {
613            localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
614                        + targetAssociationId);
615            mStateMachine.startConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
616        }
617    }
618
619    // Helper for selecting the band for connectivity scan
620    private int getScanBand() {
621        return getScanBand(true);
622    }
623
624    private int getScanBand(boolean isFullBandScan) {
625        if (isFullBandScan) {
626            return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
627        } else {
628            // Use channel list instead.
629            return WifiScanner.WIFI_BAND_UNSPECIFIED;
630        }
631    }
632
633    // Helper for setting the channels for connectivity scan when band is unspecified. Returns
634    // false if we can't retrieve the info.
635    private boolean setScanChannels(ScanSettings settings) {
636        WifiConfiguration config = mStateMachine.getCurrentWifiConfiguration();
637
638        if (config == null) {
639            return false;
640        }
641
642        Set<Integer> freqs =
643                mConfigManager.fetchChannelSetForNetworkForPartialScan(
644                        config.networkId, CHANNEL_LIST_AGE_MS);
645
646        if (freqs != null && freqs.size() != 0) {
647            int index = 0;
648            settings.channels = new WifiScanner.ChannelSpec[freqs.size()];
649            for (Integer freq : freqs) {
650                settings.channels[index++] = new WifiScanner.ChannelSpec(freq);
651            }
652            return true;
653        } else {
654            localLog("No scan channels for " + config.configKey() + ". Perform full band scan");
655            return false;
656        }
657    }
658
659    // Watchdog timer handler
660    private void watchdogHandler() {
661        localLog("watchdogHandler");
662
663        // Schedule the next timer and start a single scan if we are in disconnected state.
664        // Otherwise, the watchdog timer will be scheduled when entering disconnected
665        // state.
666        if (mWifiState == WIFI_STATE_DISCONNECTED) {
667            localLog("start a single scan from watchdogHandler");
668
669            scheduleWatchdogTimer();
670            startSingleScan(true);
671        }
672    }
673
674    // Start a single scan and set up the interval for next single scan.
675    private void startPeriodicSingleScan() {
676        long currentTimeStamp = mClock.getElapsedSinceBootMillis();
677
678        if (mLastPeriodicSingleScanTimeStamp != RESET_TIME_STAMP) {
679            long msSinceLastScan = currentTimeStamp - mLastPeriodicSingleScanTimeStamp;
680            if (msSinceLastScan < PERIODIC_SCAN_INTERVAL_MS) {
681                localLog("Last periodic single scan started " + msSinceLastScan
682                        + "ms ago, defer this new scan request.");
683                schedulePeriodicScanTimer(PERIODIC_SCAN_INTERVAL_MS - (int) msSinceLastScan);
684                return;
685            }
686        }
687
688        boolean isFullBandScan = true;
689
690        // If the WiFi traffic is heavy, only partial scan is initiated.
691        if (mWifiState == WIFI_STATE_CONNECTED
692                && (mWifiInfo.txSuccessRate > MAX_TX_PACKET_FOR_FULL_SCANS
693                    || mWifiInfo.rxSuccessRate > MAX_RX_PACKET_FOR_FULL_SCANS)) {
694            localLog("No full band scan due to heavy traffic, txSuccessRate="
695                        + mWifiInfo.txSuccessRate + " rxSuccessRate="
696                        + mWifiInfo.rxSuccessRate);
697            isFullBandScan = false;
698        }
699
700        mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
701        startSingleScan(isFullBandScan);
702        schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
703
704        // Set up the next scan interval in an exponential backoff fashion.
705        mPeriodicSingleScanInterval *= 2;
706        if (mPeriodicSingleScanInterval >  MAX_PERIODIC_SCAN_INTERVAL_MS) {
707            mPeriodicSingleScanInterval = MAX_PERIODIC_SCAN_INTERVAL_MS;
708        }
709    }
710
711    // Reset the last periodic single scan time stamp so that the next periodic single
712    // scan can start immediately.
713    private void resetLastPeriodicSingleScanTimeStamp() {
714        mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
715    }
716
717    // Periodic scan timer handler
718    private void periodicScanTimerHandler() {
719        localLog("periodicScanTimerHandler");
720
721        // Schedule the next timer and start a single scan if screen is on.
722        if (mScreenOn) {
723            startPeriodicSingleScan();
724        }
725    }
726
727    // Start a single scan
728    private void startSingleScan(boolean isFullBandScan) {
729        if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
730            return;
731        }
732
733        mPnoScanListener.resetLowRssiNetworkRetryDelay();
734
735        ScanSettings settings = new ScanSettings();
736        if (!isFullBandScan) {
737            if (!setScanChannels(settings)) {
738                isFullBandScan = true;
739            }
740        }
741        settings.band = getScanBand(isFullBandScan);
742        settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
743                            | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
744        settings.numBssidsPerScan = 0;
745
746        List<ScanSettings.HiddenNetwork> hiddenNetworkList =
747                mConfigManager.retrieveHiddenNetworkList();
748        settings.hiddenNetworks =
749                hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
750
751        SingleScanListener singleScanListener =
752                new SingleScanListener(isFullBandScan);
753        mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
754    }
755
756    // Start a periodic scan when screen is on
757    private void startPeriodicScan(boolean scanImmediately) {
758        mPnoScanListener.resetLowRssiNetworkRetryDelay();
759
760        // No connectivity scan if auto roaming is disabled.
761        if (mWifiState == WIFI_STATE_CONNECTED && !mEnableAutoJoinWhenAssociated) {
762            return;
763        }
764
765        // Due to b/28020168, timer based single scan will be scheduled
766        // to provide periodic scan in an exponential backoff fashion.
767        if (scanImmediately) {
768            resetLastPeriodicSingleScanTimeStamp();
769        }
770        mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
771        startPeriodicSingleScan();
772    }
773
774    // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected
775    private void startDisconnectedPnoScan() {
776        // TODO(b/29503772): Need to change this interface.
777
778        // Initialize PNO settings
779        PnoSettings pnoSettings = new PnoSettings();
780        List<PnoSettings.PnoNetwork> pnoNetworkList = mConfigManager.retrievePnoNetworkList();
781        int listSize = pnoNetworkList.size();
782
783        if (listSize == 0) {
784            // No saved network
785            localLog("No saved network for starting disconnected PNO.");
786            return;
787        }
788
789        pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize];
790        pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList);
791        pnoSettings.min5GHzRssi = mMin5GHzRssi;
792        pnoSettings.min24GHzRssi = mMin24GHzRssi;
793        pnoSettings.initialScoreMax = mInitialScoreMax;
794        pnoSettings.currentConnectionBonus = mCurrentConnectionBonus;
795        pnoSettings.sameNetworkBonus = mSameNetworkBonus;
796        pnoSettings.secureBonus = mSecureBonus;
797        pnoSettings.band5GHzBonus = mBand5GHzBonus;
798
799        // Initialize scan settings
800        ScanSettings scanSettings = new ScanSettings();
801        scanSettings.band = getScanBand();
802        scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
803        scanSettings.numBssidsPerScan = 0;
804        scanSettings.periodInMs = DISCONNECTED_PNO_SCAN_INTERVAL_MS;
805
806        mPnoScanListener.clearScanDetails();
807
808        mScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
809        mPnoScanStarted = true;
810    }
811
812    // Stop PNO scan.
813    private void stopPnoScan() {
814        if (mPnoScanStarted) {
815            mScanner.stopPnoScan(mPnoScanListener);
816        }
817
818        mPnoScanStarted = false;
819    }
820
821    // Set up watchdog timer
822    private void scheduleWatchdogTimer() {
823        localLog("scheduleWatchdogTimer");
824
825        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
826                            mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS,
827                            WATCHDOG_TIMER_TAG,
828                            mWatchdogListener, mEventHandler);
829    }
830
831    // Set up periodic scan timer
832    private void schedulePeriodicScanTimer(int intervalMs) {
833        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
834                            mClock.getElapsedSinceBootMillis() + intervalMs,
835                            PERIODIC_SCAN_TIMER_TAG,
836                            mPeriodicScanTimerListener, mEventHandler);
837        mPeriodicScanTimerSet = true;
838    }
839
840    // Cancel periodic scan timer
841    private void cancelPeriodicScanTimer() {
842        if (mPeriodicScanTimerSet) {
843            mAlarmManager.cancel(mPeriodicScanTimerListener);
844            mPeriodicScanTimerSet = false;
845        }
846    }
847
848    // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS
849    private void scheduleDelayedSingleScan(boolean isFullBandScan) {
850        localLog("scheduleDelayedSingleScan");
851
852        RestartSingleScanListener restartSingleScanListener =
853                new RestartSingleScanListener(isFullBandScan);
854        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
855                            mClock.getElapsedSinceBootMillis() + RESTART_SCAN_DELAY_MS,
856                            RESTART_SINGLE_SCAN_TIMER_TAG,
857                            restartSingleScanListener, mEventHandler);
858    }
859
860    // Set up timer to start a delayed scan after msFromNow milli-seconds
861    private void scheduleDelayedConnectivityScan(int msFromNow) {
862        localLog("scheduleDelayedConnectivityScan");
863
864        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
865                            mClock.getElapsedSinceBootMillis() + msFromNow,
866                            RESTART_CONNECTIVITY_SCAN_TIMER_TAG,
867                            mRestartScanListener, mEventHandler);
868
869    }
870
871    // Start a connectivity scan. The scan method is chosen according to
872    // the current screen state and WiFi state.
873    private void startConnectivityScan(boolean scanImmediately) {
874        localLog("startConnectivityScan: screenOn=" + mScreenOn
875                        + " wifiState=" + mWifiState
876                        + " scanImmediately=" + scanImmediately
877                        + " wifiEnabled=" + mWifiEnabled
878                        + " wifiConnectivityManagerEnabled="
879                        + mWifiConnectivityManagerEnabled);
880
881        if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
882            return;
883        }
884
885        // Always stop outstanding connecivity scan if there is any
886        stopConnectivityScan();
887
888        // Don't start a connectivity scan while Wifi is in the transition
889        // between connected and disconnected states.
890        if (mWifiState != WIFI_STATE_CONNECTED && mWifiState != WIFI_STATE_DISCONNECTED) {
891            return;
892        }
893
894        if (mScreenOn) {
895            startPeriodicScan(scanImmediately);
896        } else { // screenOff
897            if (mWifiState == WIFI_STATE_DISCONNECTED) {
898                startDisconnectedPnoScan();
899            }
900        }
901    }
902
903    // Stop connectivity scan if there is any.
904    private void stopConnectivityScan() {
905        // Due to b/28020168, timer based single scan will be scheduled
906        // to provide periodic scan in an exponential backoff fashion.
907        cancelPeriodicScanTimer();
908        stopPnoScan();
909        mScanRestartCount = 0;
910    }
911
912    /**
913     * Handler for screen state (on/off) changes
914     */
915    public void handleScreenStateChanged(boolean screenOn) {
916        localLog("handleScreenStateChanged: screenOn=" + screenOn);
917
918        mScreenOn = screenOn;
919
920        startConnectivityScan(SCAN_ON_SCHEDULE);
921    }
922
923    /**
924     * Handler for WiFi state (connected/disconnected) changes
925     */
926    public void handleConnectionStateChanged(int state) {
927        localLog("handleConnectionStateChanged: state=" + state);
928
929        mWifiState = state;
930
931        // Reset BSSID of last connection attempt and kick off
932        // the watchdog timer if entering disconnected state.
933        if (mWifiState == WIFI_STATE_DISCONNECTED) {
934            mLastConnectionAttemptBssid = null;
935            scheduleWatchdogTimer();
936        }
937
938        startConnectivityScan(SCAN_ON_SCHEDULE);
939    }
940
941    /**
942     * Handler when user toggles whether untrusted connection is allowed
943     */
944    public void setUntrustedConnectionAllowed(boolean allowed) {
945        localLog("setUntrustedConnectionAllowed: allowed=" + allowed);
946
947        if (mUntrustedConnectionAllowed != allowed) {
948            mUntrustedConnectionAllowed = allowed;
949            startConnectivityScan(SCAN_IMMEDIATELY);
950        }
951    }
952
953    /**
954     * Handler when user specifies a particular network to connect to
955     */
956    public void setUserConnectChoice(int netId) {
957        localLog("setUserConnectChoice: netId=" + netId);
958
959        mNetworkSelector.setUserConnectChoice(netId);
960        clearConnectionAttemptTimeStamps();
961    }
962
963    /**
964     * Handler for on-demand connectivity scan
965     */
966    public void forceConnectivityScan() {
967        localLog("forceConnectivityScan");
968
969        startConnectivityScan(SCAN_IMMEDIATELY);
970    }
971
972    /**
973     * Track whether a BSSID should be enabled or disabled for WifiNetworkSelector
974     */
975    public boolean trackBssid(String bssid, boolean enable) {
976        localLog("trackBssid: " + (enable ? "enable " : "disable ") + bssid);
977
978        boolean ret = mNetworkSelector
979                            .enableBssidForNetworkSelection(bssid, enable);
980
981        if (ret && !enable) {
982            // Disabling a BSSID can happen when the AP candidate to connect to has
983            // no capacity for new stations. We start another scan immediately so that
984            // WifiNetworkSelector can give us another candidate to connect to.
985            startConnectivityScan(SCAN_IMMEDIATELY);
986        }
987
988        return ret;
989    }
990
991    /**
992     * Inform WiFi is enabled for connection or not
993     */
994    public void setWifiEnabled(boolean enable) {
995        localLog("Set WiFi " + (enable ? "enabled" : "disabled"));
996
997        mWifiEnabled = enable;
998
999        if (!mWifiEnabled) {
1000            stopConnectivityScan();
1001            resetLastPeriodicSingleScanTimeStamp();
1002            mLastConnectionAttemptBssid = null;
1003        } else if (mWifiConnectivityManagerEnabled) {
1004            startConnectivityScan(SCAN_IMMEDIATELY);
1005        }
1006    }
1007
1008    /**
1009     * Turn on/off the WifiConnectivityMangager at runtime
1010     */
1011    public void enable(boolean enable) {
1012        localLog("Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled"));
1013
1014        mWifiConnectivityManagerEnabled = enable;
1015
1016        if (!mWifiConnectivityManagerEnabled) {
1017            stopConnectivityScan();
1018            resetLastPeriodicSingleScanTimeStamp();
1019            mLastConnectionAttemptBssid = null;
1020        } else if (mWifiEnabled) {
1021            startConnectivityScan(SCAN_IMMEDIATELY);
1022        }
1023    }
1024
1025    @VisibleForTesting
1026    int getLowRssiNetworkRetryDelay() {
1027        return mPnoScanListener.getLowRssiNetworkRetryDelay();
1028    }
1029
1030    @VisibleForTesting
1031    long getLastPeriodicSingleScanTimeStamp() {
1032        return mLastPeriodicSingleScanTimeStamp;
1033    }
1034}
1035