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