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