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