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