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