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