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