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