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