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