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