WifiConnectivityManagerTest.java revision 2dfadf313b3e5194d56d2fa24b9270086cda165a
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.WifiConfigurationTestUtil.generateWifiConfig;
20import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE;
21
22import static org.junit.Assert.*;
23import static org.mockito.Mockito.*;
24
25import android.app.test.MockAnswerUtil.AnswerWithArguments;
26import android.app.test.TestAlarmManager;
27import android.content.Context;
28import android.content.res.Resources;
29import android.net.wifi.ScanResult;
30import android.net.wifi.ScanResult.InformationElement;
31import android.net.wifi.SupplicantState;
32import android.net.wifi.WifiConfiguration;
33import android.net.wifi.WifiInfo;
34import android.net.wifi.WifiScanner;
35import android.net.wifi.WifiScanner.PnoScanListener;
36import android.net.wifi.WifiScanner.PnoSettings;
37import android.net.wifi.WifiScanner.ScanListener;
38import android.net.wifi.WifiScanner.ScanSettings;
39import android.net.wifi.WifiSsid;
40import android.os.SystemClock;
41import android.os.WorkSource;
42import android.os.test.TestLooper;
43import android.test.suitebuilder.annotation.SmallTest;
44
45import com.android.internal.R;
46
47import org.junit.After;
48import org.junit.Before;
49import org.junit.Test;
50import org.mockito.ArgumentCaptor;
51
52import java.nio.charset.StandardCharsets;
53import java.util.ArrayList;
54import java.util.HashSet;
55
56/**
57 * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
58 */
59@SmallTest
60public class WifiConnectivityManagerTest {
61
62    /**
63     * Called before each test
64     */
65    @Before
66    public void setUp() throws Exception {
67        mWifiInjector = mockWifiInjector();
68        mResource = mockResource();
69        mAlarmManager = new TestAlarmManager();
70        mContext = mockContext();
71        mWifiStateMachine = mockWifiStateMachine();
72        mWifiConfigManager = mockWifiConfigManager();
73        mWifiInfo = getWifiInfo();
74        mWifiScanner = mockWifiScanner();
75        mWifiNS = mockWifiNetworkSelector();
76        mWifiConnectivityManager = createConnectivityManager();
77        mWifiConnectivityManager.setWifiEnabled(true);
78        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
79    }
80
81    /**
82     * Called after each test
83     */
84    @After
85    public void cleanup() {
86        validateMockitoUsage();
87    }
88
89    private Resources mResource;
90    private Context mContext;
91    private TestAlarmManager mAlarmManager;
92    private TestLooper mLooper = new TestLooper();
93    private WifiConnectivityManager mWifiConnectivityManager;
94    private WifiNetworkSelector mWifiNS;
95    private WifiStateMachine mWifiStateMachine;
96    private WifiScanner mWifiScanner;
97    private WifiConfigManager mWifiConfigManager;
98    private WifiInfo mWifiInfo;
99    private Clock mClock = mock(Clock.class);
100    private WifiLastResortWatchdog mWifiLastResortWatchdog;
101    private WifiMetrics mWifiMetrics;
102    private WifiInjector mWifiInjector;
103    private MockResources mResources;
104
105    private static final int CANDIDATE_NETWORK_ID = 0;
106    private static final String CANDIDATE_SSID = "\"AnSsid\"";
107    private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
108    private static final String TAG = "WifiConnectivityManager Unit Test";
109    private static final long CURRENT_SYSTEM_TIME_MS = 1000;
110
111    Resources mockResource() {
112        Resources resource = mock(Resources.class);
113
114        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
115        when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
116        when(resource.getBoolean(
117                R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
118        when(resource.getInteger(
119                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
120                .thenReturn(-60);
121        when(resource.getInteger(
122                R.integer.config_wifi_framework_current_network_boost))
123                .thenReturn(16);
124        return resource;
125    }
126
127    Context mockContext() {
128        Context context = mock(Context.class);
129
130        when(context.getResources()).thenReturn(mResource);
131        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
132                mAlarmManager.getAlarmManager());
133
134        return context;
135    }
136
137    WifiScanner mockWifiScanner() {
138        WifiScanner scanner = mock(WifiScanner.class);
139        ArgumentCaptor<ScanListener> allSingleScanListenerCaptor =
140                ArgumentCaptor.forClass(ScanListener.class);
141
142        doNothing().when(scanner).registerScanListener(allSingleScanListenerCaptor.capture());
143
144        // dummy scan results. QNS PeriodicScanListener bulids scanDetails from
145        // the fullScanResult and doesn't really use results
146        final WifiScanner.ScanData[] scanDatas = new WifiScanner.ScanData[1];
147
148        // do a synchronous answer for the ScanListener callbacks
149        doAnswer(new AnswerWithArguments() {
150                public void answer(ScanSettings settings, ScanListener listener,
151                        WorkSource workSource) throws Exception {
152                    listener.onResults(scanDatas);
153                }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
154
155        doAnswer(new AnswerWithArguments() {
156                public void answer(ScanSettings settings, ScanListener listener,
157                        WorkSource workSource) throws Exception {
158                    listener.onResults(scanDatas);
159                    allSingleScanListenerCaptor.getValue().onResults(scanDatas);
160                }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
161
162        // This unfortunately needs to be a somewhat valid scan result, otherwise
163        // |ScanDetailUtil.toScanDetail| raises exceptions.
164        final ScanResult[] scanResults = new ScanResult[1];
165        scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
166                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps",
167                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
168        scanResults[0].informationElements = new InformationElement[1];
169        scanResults[0].informationElements[0] = new InformationElement();
170        scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
171        scanResults[0].informationElements[0].bytes =
172                CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
173
174        doAnswer(new AnswerWithArguments() {
175            public void answer(ScanSettings settings, PnoSettings pnoSettings,
176                    PnoScanListener listener) throws Exception {
177                listener.onPnoNetworkFound(scanResults);
178            }}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject());
179
180        doAnswer(new AnswerWithArguments() {
181            public void answer(ScanSettings settings, PnoSettings pnoSettings,
182                    PnoScanListener listener) throws Exception {
183                listener.onPnoNetworkFound(scanResults);
184            }}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject());
185
186        return scanner;
187    }
188
189    WifiStateMachine mockWifiStateMachine() {
190        WifiStateMachine stateMachine = mock(WifiStateMachine.class);
191
192        when(stateMachine.isLinkDebouncing()).thenReturn(false);
193        when(stateMachine.isConnected()).thenReturn(false);
194        when(stateMachine.isDisconnected()).thenReturn(true);
195        when(stateMachine.isSupplicantTransientState()).thenReturn(false);
196
197        return stateMachine;
198    }
199
200    WifiNetworkSelector mockWifiNetworkSelector() {
201        WifiNetworkSelector ns = mock(WifiNetworkSelector.class);
202
203        WifiConfiguration candidate = generateWifiConfig(
204                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
205        candidate.BSSID = CANDIDATE_BSSID;
206        ScanResult candidateScanResult = new ScanResult();
207        candidateScanResult.SSID = CANDIDATE_SSID;
208        candidateScanResult.BSSID = CANDIDATE_BSSID;
209        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
210
211        when(ns.selectNetwork(anyObject(), anyObject(), anyBoolean(), anyBoolean(),
212              anyBoolean())).thenReturn(candidate);
213        return ns;
214    }
215
216    WifiInfo getWifiInfo() {
217        WifiInfo wifiInfo = new WifiInfo();
218
219        wifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
220        wifiInfo.setBSSID(null);
221        wifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
222
223        return wifiInfo;
224    }
225
226    WifiConfigManager mockWifiConfigManager() {
227        WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
228
229        when(wifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
230
231        // Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
232        PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
233        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
234        pnoNetworkList.add(pnoNetwork);
235        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
236        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
237
238        return wifiConfigManager;
239    }
240
241    WifiInjector mockWifiInjector() {
242        WifiInjector wifiInjector = mock(WifiInjector.class);
243        mWifiLastResortWatchdog = mock(WifiLastResortWatchdog.class);
244        mWifiMetrics = mock(WifiMetrics.class);
245        when(wifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
246        when(wifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
247        when(wifiInjector.getClock()).thenReturn(mClock);
248        return wifiInjector;
249    }
250
251    WifiConnectivityManager createConnectivityManager() {
252        return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
253                mWifiConfigManager, mWifiInfo, mWifiNS, mWifiInjector, mLooper.getLooper(), true);
254    }
255
256    /**
257     *  Wifi enters disconnected state while screen is on.
258     *
259     * Expected behavior: WifiConnectivityManager calls
260     * WifiStateMachine.startConnectToNetwork() with the
261     * expected candidate network ID and BSSID.
262     */
263    @Test
264    public void enterWifiDisconnectedStateWhenScreenOn() {
265        // Set screen to on
266        mWifiConnectivityManager.handleScreenStateChanged(true);
267
268        // Set WiFi to disconnected state
269        mWifiConnectivityManager.handleConnectionStateChanged(
270                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
271
272        verify(mWifiStateMachine).startConnectToNetwork(
273                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
274    }
275
276    /**
277     *  Wifi enters connected state while screen is on.
278     *
279     * Expected behavior: WifiConnectivityManager calls
280     * WifiStateMachine.startConnectToNetwork() with the
281     * expected candidate network ID and BSSID.
282     */
283    @Test
284    public void enterWifiConnectedStateWhenScreenOn() {
285        // Set screen to on
286        mWifiConnectivityManager.handleScreenStateChanged(true);
287
288        // Set WiFi to connected state
289        mWifiConnectivityManager.handleConnectionStateChanged(
290                WifiConnectivityManager.WIFI_STATE_CONNECTED);
291
292        verify(mWifiStateMachine).startConnectToNetwork(
293                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
294    }
295
296    /**
297     *  Screen turned on while WiFi in disconnected state.
298     *
299     * Expected behavior: WifiConnectivityManager calls
300     * WifiStateMachine.startConnectToNetwork() with the
301     * expected candidate network ID and BSSID.
302     */
303    @Test
304    public void turnScreenOnWhenWifiInDisconnectedState() {
305        // Set WiFi to disconnected state
306        mWifiConnectivityManager.handleConnectionStateChanged(
307                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
308
309        // Set screen to on
310        mWifiConnectivityManager.handleScreenStateChanged(true);
311
312        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
313                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
314    }
315
316    /**
317     *  Screen turned on while WiFi in connected state.
318     *
319     * Expected behavior: WifiConnectivityManager calls
320     * WifiStateMachine.startConnectToNetwork() with the
321     * expected candidate network ID and BSSID.
322     */
323    @Test
324    public void turnScreenOnWhenWifiInConnectedState() {
325        // Set WiFi to connected state
326        mWifiConnectivityManager.handleConnectionStateChanged(
327                WifiConnectivityManager.WIFI_STATE_CONNECTED);
328
329        // Set screen to on
330        mWifiConnectivityManager.handleScreenStateChanged(true);
331
332        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
333                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
334    }
335
336    /**
337     *  Screen turned on while WiFi in connected state but
338     *  auto roaming is disabled.
339     *
340     * Expected behavior: WifiConnectivityManager doesn't invoke
341     * WifiStateMachine.startConnectToNetwork() because roaming
342     * is turned off.
343     */
344    @Test
345    public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() {
346        // Turn off auto roaming
347        when(mResource.getBoolean(
348                R.bool.config_wifi_framework_enable_associated_network_selection))
349                .thenReturn(false);
350        mWifiConnectivityManager = createConnectivityManager();
351
352        // Set WiFi to connected state
353        mWifiConnectivityManager.handleConnectionStateChanged(
354                WifiConnectivityManager.WIFI_STATE_CONNECTED);
355
356        // Set screen to on
357        mWifiConnectivityManager.handleScreenStateChanged(true);
358
359        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
360                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
361    }
362
363    /**
364     * Multiple back to back connection attempts within the rate interval should be rate limited.
365     *
366     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
367     * with the expected candidate network ID and BSSID for only the expected number of times within
368     * the given interval.
369     */
370    @Test
371    public void connectionAttemptRateLimitedWhenScreenOff() {
372        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
373        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
374        int numAttempts = 0;
375        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
376
377        mWifiConnectivityManager.handleScreenStateChanged(false);
378
379        // First attempt the max rate number of connections within the rate interval.
380        long currentTimeStamp = 0;
381        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
382            currentTimeStamp += connectionAttemptIntervals;
383            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
384            // Set WiFi to disconnected state to trigger PNO scan
385            mWifiConnectivityManager.handleConnectionStateChanged(
386                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
387            numAttempts++;
388        }
389        // Now trigger another connection attempt before the rate interval, this should be
390        // skipped because we've crossed rate limit.
391        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
392        // Set WiFi to disconnected state to trigger PNO scan
393        mWifiConnectivityManager.handleConnectionStateChanged(
394                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
395
396        // Verify that we attempt to connect upto the rate.
397        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
398                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
399    }
400
401    /**
402     * Multiple back to back connection attempts outside the rate interval should not be rate
403     * limited.
404     *
405     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
406     * with the expected candidate network ID and BSSID for only the expected number of times within
407     * the given interval.
408     */
409    @Test
410    public void connectionAttemptNotRateLimitedWhenScreenOff() {
411        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
412        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
413        int numAttempts = 0;
414        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
415
416        mWifiConnectivityManager.handleScreenStateChanged(false);
417
418        // First attempt the max rate number of connections within the rate interval.
419        long currentTimeStamp = 0;
420        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
421            currentTimeStamp += connectionAttemptIntervals;
422            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
423            // Set WiFi to disconnected state to trigger PNO scan
424            mWifiConnectivityManager.handleConnectionStateChanged(
425                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
426            numAttempts++;
427        }
428        // Now trigger another connection attempt after the rate interval, this should not be
429        // skipped because we should've evicted the older attempt.
430        when(mClock.getElapsedSinceBootMillis()).thenReturn(
431                currentTimeStamp + connectionAttemptIntervals * 2);
432        // Set WiFi to disconnected state to trigger PNO scan
433        mWifiConnectivityManager.handleConnectionStateChanged(
434                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
435        numAttempts++;
436
437        // Verify that all the connection attempts went through
438        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
439                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
440    }
441
442    /**
443     * Multiple back to back connection attempts after a user selection should not be rate limited.
444     *
445     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
446     * with the expected candidate network ID and BSSID for only the expected number of times within
447     * the given interval.
448     */
449    @Test
450    public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() {
451        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
452        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
453        int numAttempts = 0;
454        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
455
456        mWifiConnectivityManager.handleScreenStateChanged(false);
457
458        // First attempt the max rate number of connections within the rate interval.
459        long currentTimeStamp = 0;
460        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
461            currentTimeStamp += connectionAttemptIntervals;
462            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
463            // Set WiFi to disconnected state to trigger PNO scan
464            mWifiConnectivityManager.handleConnectionStateChanged(
465                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
466            numAttempts++;
467        }
468
469        mWifiConnectivityManager.setUserConnectChoice(CANDIDATE_NETWORK_ID);
470
471        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
472            currentTimeStamp += connectionAttemptIntervals;
473            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
474            // Set WiFi to disconnected state to trigger PNO scan
475            mWifiConnectivityManager.handleConnectionStateChanged(
476                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
477            numAttempts++;
478        }
479
480        // Verify that all the connection attempts went through
481        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
482                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
483    }
484
485    /**
486     *  PNO retry for low RSSI networks.
487     *
488     * Expected behavior: WifiConnectivityManager doubles the low RSSI
489     * network retry delay value after QNS skips the PNO scan results
490     * because of their low RSSI values.
491     */
492    @Test
493    public void pnoRetryForLowRssiNetwork() {
494        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyBoolean(), anyBoolean(),
495              anyBoolean())).thenReturn(null);
496
497        // Set screen to off
498        mWifiConnectivityManager.handleScreenStateChanged(false);
499
500        // Get the current retry delay value
501        int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager
502                .getLowRssiNetworkRetryDelay();
503
504        // Set WiFi to disconnected state to trigger PNO scan
505        mWifiConnectivityManager.handleConnectionStateChanged(
506                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
507
508        // Get the retry delay value after QNS didn't select a
509        // network candicate from the PNO scan results.
510        int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager
511                .getLowRssiNetworkRetryDelay();
512
513        assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
514                lowRssiNetworkRetryDelayAfterPnoValue);
515    }
516
517    /**
518     * Ensure that the watchdog bite increments the "Pno bad" metric.
519     *
520     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
521     * a candidate while watchdog single scan did.
522     */
523    @Test
524    public void watchdogBitePnoBadIncrementsMetrics() {
525        // Set screen to off
526        mWifiConnectivityManager.handleScreenStateChanged(false);
527
528        // Set WiFi to disconnected state to trigger PNO scan
529        mWifiConnectivityManager.handleConnectionStateChanged(
530                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
531
532        // Now fire the watchdog alarm and verify the metrics were incremented.
533        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
534        mLooper.dispatchAll();
535
536        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoBad();
537        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoGood();
538    }
539
540    /**
541     * Ensure that the watchdog bite increments the "Pno good" metric.
542     *
543     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
544     * a candidate which was the same with watchdog single scan.
545     */
546    @Test
547    public void watchdogBitePnoGoodIncrementsMetrics() {
548        // Qns returns no candidate after watchdog single scan.
549        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyBoolean(), anyBoolean(),
550              anyBoolean())).thenReturn(null);
551
552        // Set screen to off
553        mWifiConnectivityManager.handleScreenStateChanged(false);
554
555        // Set WiFi to disconnected state to trigger PNO scan
556        mWifiConnectivityManager.handleConnectionStateChanged(
557                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
558
559        // Now fire the watchdog alarm and verify the metrics were incremented.
560        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
561        mLooper.dispatchAll();
562
563        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoGood();
564        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoBad();
565    }
566
567    /**
568     *  Verify that scan interval for screen on and wifi disconnected scenario
569     *  is in the exponential backoff fashion.
570     *
571     * Expected behavior: WifiConnectivityManager doubles periodic
572     * scan interval.
573     */
574    @Test
575    public void checkPeriodicScanIntervalWhenDisconnected() {
576        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
577        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
578
579        // Set screen to ON
580        mWifiConnectivityManager.handleScreenStateChanged(true);
581
582        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
583        // by screen state change can settle
584        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
585        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
586
587        // Set WiFi to disconnected state to trigger periodic scan
588        mWifiConnectivityManager.handleConnectionStateChanged(
589                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
590
591        // Get the first periodic scan interval
592        long firstIntervalMs = mAlarmManager
593                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
594                    - currentTimeStamp;
595        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
596
597        currentTimeStamp += firstIntervalMs;
598        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
599
600        // Now fire the first periodic scan alarm timer
601        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
602        mLooper.dispatchAll();
603
604        // Get the second periodic scan interval
605        long secondIntervalMs = mAlarmManager
606                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
607                    - currentTimeStamp;
608
609        // Verify the intervals are exponential back off
610        assertEquals(firstIntervalMs * 2, secondIntervalMs);
611
612        currentTimeStamp += secondIntervalMs;
613        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
614
615        // Make sure we eventually stay at the maximum scan interval.
616        long intervalMs = 0;
617        for (int i = 0; i < 5; i++) {
618            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
619            mLooper.dispatchAll();
620            intervalMs = mAlarmManager
621                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
622                    - currentTimeStamp;
623            currentTimeStamp += intervalMs;
624            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
625        }
626
627        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
628    }
629
630    /**
631     *  Verify that scan interval for screen on and wifi connected scenario
632     *  is in the exponential backoff fashion.
633     *
634     * Expected behavior: WifiConnectivityManager doubles periodic
635     * scan interval.
636     */
637    @Test
638    public void checkPeriodicScanIntervalWhenConnected() {
639        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
640        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
641
642        // Set screen to ON
643        mWifiConnectivityManager.handleScreenStateChanged(true);
644
645        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
646        // by screen state change can settle
647        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
648        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
649
650        // Set WiFi to connected state to trigger periodic scan
651        mWifiConnectivityManager.handleConnectionStateChanged(
652                WifiConnectivityManager.WIFI_STATE_CONNECTED);
653
654        // Get the first periodic scan interval
655        long firstIntervalMs = mAlarmManager
656                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
657                    - currentTimeStamp;
658        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
659
660        currentTimeStamp += firstIntervalMs;
661        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
662
663        // Now fire the first periodic scan alarm timer
664        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
665        mLooper.dispatchAll();
666
667        // Get the second periodic scan interval
668        long secondIntervalMs = mAlarmManager
669                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
670                    - currentTimeStamp;
671
672        // Verify the intervals are exponential back off
673        assertEquals(firstIntervalMs * 2, secondIntervalMs);
674
675        currentTimeStamp += secondIntervalMs;
676        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
677
678        // Make sure we eventually stay at the maximum scan interval.
679        long intervalMs = 0;
680        for (int i = 0; i < 5; i++) {
681            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
682            mLooper.dispatchAll();
683            intervalMs = mAlarmManager
684                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
685                    - currentTimeStamp;
686            currentTimeStamp += intervalMs;
687            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
688        }
689
690        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
691    }
692
693    /**
694     *  When screen on trigger two connection state change events back to back to
695     *  verify that the minium scan interval is enforced.
696     *
697     * Expected behavior: WifiConnectivityManager start the second periodic single
698     * scan PERIODIC_SCAN_INTERVAL_MS after the first one.
699     */
700    @Test
701    public void checkMinimumPeriodicScanIntervalWhenScreenOn() {
702        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
703        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
704
705        // Set screen to ON
706        mWifiConnectivityManager.handleScreenStateChanged(true);
707
708        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
709        // by screen state change can settle
710        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
711        long firstScanTimeStamp = currentTimeStamp;
712        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
713
714        // Set WiFi to connected state to trigger the periodic scan
715        mWifiConnectivityManager.handleConnectionStateChanged(
716                WifiConnectivityManager.WIFI_STATE_CONNECTED);
717
718        // Set the second scan attempt time stamp.
719        currentTimeStamp += 2000;
720        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
721
722        // Set WiFi to disconnected state to trigger another periodic scan
723        mWifiConnectivityManager.handleConnectionStateChanged(
724                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
725
726        // Get the second periodic scan actual time stamp
727        long secondScanTimeStamp = mAlarmManager
728                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
729
730        // Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after the
731        // very first scan.
732        assertEquals(secondScanTimeStamp, firstScanTimeStamp
733                       + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
734
735    }
736
737    /**
738     *  When screen on trigger a connection state change event and a forced connectivity
739     *  scan event back to back to verify that the minimum scan interval is not applied
740     *  in this scenario.
741     *
742     * Expected behavior: WifiConnectivityManager starts the second periodic single
743     * scan immediately.
744     */
745    @Test
746    public void checkMinimumPeriodicScanIntervalNotEnforced() {
747        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
748        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
749
750        // Set screen to ON
751        mWifiConnectivityManager.handleScreenStateChanged(true);
752
753        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
754        // by screen state change can settle
755        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
756        long firstScanTimeStamp = currentTimeStamp;
757        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
758
759        // Set WiFi to connected state to trigger the periodic scan
760        mWifiConnectivityManager.handleConnectionStateChanged(
761                WifiConnectivityManager.WIFI_STATE_CONNECTED);
762
763        // Set the second scan attempt time stamp
764        currentTimeStamp += 2000;
765        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
766
767        // Force a connectivity scan
768        mWifiConnectivityManager.forceConnectivityScan();
769
770        // Get the second periodic scan actual time stamp. Note, this scan is not
771        // started from the AlarmManager.
772        long secondScanTimeStamp = mWifiConnectivityManager.getLastPeriodicSingleScanTimeStamp();
773
774        // Verify that the second scan is fired immediately
775        assertEquals(secondScanTimeStamp, currentTimeStamp);
776    }
777
778    /**
779     * Verify that we perform full band scan when the currently connected network's tx/rx success
780     * rate is low.
781     *
782     * Expected behavior: WifiConnectivityManager does full band scan.
783     */
784    @Test
785    public void checkSingleScanSettingsWhenConnectedWithLowDataRate() {
786        mWifiInfo.txSuccessRate = 0;
787        mWifiInfo.rxSuccessRate = 0;
788
789        final HashSet<Integer> channelList = new HashSet<>();
790        channelList.add(1);
791        channelList.add(2);
792        channelList.add(3);
793
794        when(mWifiStateMachine.getCurrentWifiConfiguration())
795                .thenReturn(new WifiConfiguration());
796        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyInt()))
797                .thenReturn(channelList);
798
799        doAnswer(new AnswerWithArguments() {
800            public void answer(ScanSettings settings, ScanListener listener,
801                    WorkSource workSource) throws Exception {
802                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
803                assertNull(settings.channels);
804            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
805
806        // Set screen to ON
807        mWifiConnectivityManager.handleScreenStateChanged(true);
808
809        // Set WiFi to connected state to trigger periodic scan
810        mWifiConnectivityManager.handleConnectionStateChanged(
811                WifiConnectivityManager.WIFI_STATE_CONNECTED);
812
813        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
814    }
815
816    /**
817     * Verify that we perform partial scan when the currently connected network's tx/rx success
818     * rate is high and when the currently connected network is present in scan
819     * cache in WifiConfigManager.
820     *
821     * Expected behavior: WifiConnectivityManager does full band scan.
822     */
823    @Test
824    public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
825        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
826        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
827
828        final HashSet<Integer> channelList = new HashSet<>();
829        channelList.add(1);
830        channelList.add(2);
831        channelList.add(3);
832
833        when(mWifiStateMachine.getCurrentWifiConfiguration())
834                .thenReturn(new WifiConfiguration());
835        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyInt()))
836                .thenReturn(channelList);
837
838        doAnswer(new AnswerWithArguments() {
839            public void answer(ScanSettings settings, ScanListener listener,
840                    WorkSource workSource) throws Exception {
841                assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED);
842                assertEquals(settings.channels.length, channelList.size());
843                for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) {
844                    assertTrue(channelList.contains(settings.channels[chanIdx].frequency));
845                }
846            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
847
848        // Set screen to ON
849        mWifiConnectivityManager.handleScreenStateChanged(true);
850
851        // Set WiFi to connected state to trigger periodic scan
852        mWifiConnectivityManager.handleConnectionStateChanged(
853                WifiConnectivityManager.WIFI_STATE_CONNECTED);
854
855        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
856    }
857
858    /**
859     * Verify that we fall back to full band scan when the currently connected network's tx/rx
860     * success rate is high and the currently connected network is not present in scan cache in
861     * WifiConfigManager. This is simulated by returning an empty hashset in |makeChannelList|.
862     *
863     * Expected behavior: WifiConnectivityManager does full band scan.
864     */
865    @Test
866    public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
867        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
868        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
869
870        final HashSet<Integer> channelList = new HashSet<>();
871
872        when(mWifiStateMachine.getCurrentWifiConfiguration())
873                .thenReturn(new WifiConfiguration());
874        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyInt()))
875                .thenReturn(channelList);
876
877        doAnswer(new AnswerWithArguments() {
878            public void answer(ScanSettings settings, ScanListener listener,
879                    WorkSource workSource) throws Exception {
880                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
881                assertNull(settings.channels);
882            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
883
884        // Set screen to ON
885        mWifiConnectivityManager.handleScreenStateChanged(true);
886
887        // Set WiFi to connected state to trigger periodic scan
888        mWifiConnectivityManager.handleConnectionStateChanged(
889                WifiConnectivityManager.WIFI_STATE_CONNECTED);
890
891        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
892    }
893
894    /**
895     *  Verify that we retry connectivity scan up to MAX_SCAN_RESTART_ALLOWED times
896     *  when Wifi somehow gets into a bad state and fails to scan.
897     *
898     * Expected behavior: WifiConnectivityManager schedules connectivity scan
899     * MAX_SCAN_RESTART_ALLOWED times.
900     */
901    @Test
902    public void checkMaximumScanRetry() {
903        // Set screen to ON
904        mWifiConnectivityManager.handleScreenStateChanged(true);
905
906        doAnswer(new AnswerWithArguments() {
907            public void answer(ScanSettings settings, ScanListener listener,
908                    WorkSource workSource) throws Exception {
909                listener.onFailure(-1, "ScanFailure");
910            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
911
912        // Set WiFi to disconnected state to trigger the single scan based periodic scan
913        mWifiConnectivityManager.handleConnectionStateChanged(
914                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
915
916        // Fire the alarm timer 2x timers
917        for (int i = 0; i < (WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED * 2); i++) {
918            mAlarmManager.dispatch(WifiConnectivityManager.RESTART_SINGLE_SCAN_TIMER_TAG);
919            mLooper.dispatchAll();
920        }
921
922        // Verify that the connectivity scan has been retried for MAX_SCAN_RESTART_ALLOWED
923        // times. Note, WifiScanner.startScan() is invoked MAX_SCAN_RESTART_ALLOWED + 1 times.
924        // The very first scan is the initial one, and the other MAX_SCAN_RESTART_ALLOWED
925        // are the retrial ones.
926        verify(mWifiScanner, times(WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED + 1)).startScan(
927                anyObject(), anyObject(), anyObject());
928    }
929
930    /**
931     * Listen to scan results not requested by WifiConnectivityManager and
932     * act on them.
933     *
934     * Expected behavior: WifiConnectivityManager calls
935     * WifiStateMachine.startConnectToNetwork() with the
936     * expected candidate network ID and BSSID.
937     */
938    @Test
939    public void listenToAllSingleScanResults() {
940        ScanSettings settings = new ScanSettings();
941        ScanListener scanListener = mock(ScanListener.class);
942
943        // Request a single scan outside of WifiConnectivityManager.
944        mWifiScanner.startScan(settings, scanListener, WIFI_WORK_SOURCE);
945
946        // Verify that WCM receives the scan results and initiates a connection
947        // to the network.
948        verify(mWifiStateMachine).startConnectToNetwork(
949                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
950    }
951}
952