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