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