WifiConnectivityManagerTest.java revision b6686e9d42895f9c2b9f4278cd892149bee04e3a
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.Captor;
56import org.mockito.Mock;
57import org.mockito.MockitoAnnotations;
58
59import java.nio.charset.StandardCharsets;
60import java.util.ArrayList;
61import java.util.HashSet;
62
63/**
64 * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
65 */
66@SmallTest
67public class WifiConnectivityManagerTest {
68    /**
69     * Called before each test
70     */
71    @Before
72    public void setUp() throws Exception {
73        MockitoAnnotations.initMocks(this);
74        mResource = mockResource();
75        mAlarmManager = new TestAlarmManager();
76        mContext = mockContext();
77        mWifiStateMachine = mockWifiStateMachine();
78        mWifiConfigManager = mockWifiConfigManager();
79        mWifiInfo = getWifiInfo();
80        mScanData = mockScanData();
81        mWifiScanner = mockWifiScanner();
82        mWifiConnectivityHelper = mockWifiConnectivityHelper();
83        mWifiNS = mockWifiNetworkSelector();
84        mWifiConnectivityManager = createConnectivityManager();
85        mWifiConnectivityManager.setWifiEnabled(true);
86        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
87    }
88
89    /**
90     * Called after each test
91     */
92    @After
93    public void cleanup() {
94        validateMockitoUsage();
95    }
96
97    private Resources mResource;
98
99    private Context mContext;
100    private TestAlarmManager mAlarmManager;
101    private TestLooper mLooper = new TestLooper();
102    private WifiConnectivityManager mWifiConnectivityManager;
103    private WifiNetworkSelector mWifiNS;
104    private WifiStateMachine mWifiStateMachine;
105    private WifiScanner mWifiScanner;
106    private WifiConnectivityHelper mWifiConnectivityHelper;
107    private ScanData mScanData;
108    private WifiConfigManager mWifiConfigManager;
109    private WifiInfo mWifiInfo;
110    @Mock private FrameworkFacade mFrameworkFacade;
111    @Mock private NetworkScoreManager mNetworkScoreManager;
112    @Mock private Clock mClock;
113    @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
114    @Mock private WifiMetrics mWifiMetrics;
115    @Mock private WifiNetworkScoreCache mScoreCache;
116    @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
117    private MockResources mResources;
118
119    private static final int CANDIDATE_NETWORK_ID = 0;
120    private static final String CANDIDATE_SSID = "\"AnSsid\"";
121    private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
122    private static final String INVALID_SCAN_RESULT_BSSID = "6c:f3:7f:ae:8c:f4";
123    private static final long CURRENT_SYSTEM_TIME_MS = 1000;
124
125    Resources mockResource() {
126        Resources resource = mock(Resources.class);
127
128        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
129        when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
130        when(resource.getBoolean(
131                R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
132        when(resource.getInteger(
133                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
134                .thenReturn(-60);
135        when(resource.getInteger(
136                R.integer.config_wifi_framework_current_network_boost)).thenReturn(16);
137        return resource;
138    }
139
140    Context mockContext() {
141        Context context = mock(Context.class);
142
143        when(context.getResources()).thenReturn(mResource);
144        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
145                mAlarmManager.getAlarmManager());
146
147        return context;
148    }
149
150    ScanData mockScanData() {
151        ScanData scanData = mock(ScanData.class);
152
153        when(scanData.isAllChannelsScanned()).thenReturn(true);
154
155        return scanData;
156    }
157
158    WifiScanner mockWifiScanner() {
159        WifiScanner scanner = mock(WifiScanner.class);
160        ArgumentCaptor<ScanListener> allSingleScanListenerCaptor =
161                ArgumentCaptor.forClass(ScanListener.class);
162
163        doNothing().when(scanner).registerScanListener(allSingleScanListenerCaptor.capture());
164
165        ScanData[] scanDatas = new ScanData[1];
166        scanDatas[0] = mScanData;
167
168        // do a synchronous answer for the ScanListener callbacks
169        doAnswer(new AnswerWithArguments() {
170            public void answer(ScanSettings settings, ScanListener listener,
171                    WorkSource workSource) throws Exception {
172                listener.onResults(scanDatas);
173            }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
174
175        doAnswer(new AnswerWithArguments() {
176            public void answer(ScanSettings settings, ScanListener listener,
177                    WorkSource workSource) throws Exception {
178                listener.onResults(scanDatas);
179                allSingleScanListenerCaptor.getValue().onResults(scanDatas);
180            }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
181
182        // This unfortunately needs to be a somewhat valid scan result, otherwise
183        // |ScanDetailUtil.toScanDetail| raises exceptions.
184        final ScanResult[] scanResults = new ScanResult[1];
185        scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
186                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps",
187                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
188        scanResults[0].informationElements = new InformationElement[1];
189        scanResults[0].informationElements[0] = new InformationElement();
190        scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
191        scanResults[0].informationElements[0].bytes =
192            CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
193
194        doAnswer(new AnswerWithArguments() {
195            public void answer(ScanSettings settings, PnoSettings pnoSettings,
196                    PnoScanListener listener) throws Exception {
197                listener.onPnoNetworkFound(scanResults);
198            }}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject());
199
200        doAnswer(new AnswerWithArguments() {
201            public void answer(ScanSettings settings, PnoSettings pnoSettings,
202                    PnoScanListener listener) throws Exception {
203                listener.onPnoNetworkFound(scanResults);
204            }}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject());
205
206        return scanner;
207    }
208
209    WifiConnectivityHelper mockWifiConnectivityHelper() {
210        WifiConnectivityHelper connectivityHelper = mock(WifiConnectivityHelper.class);
211
212        when(connectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
213
214        return connectivityHelper;
215    }
216
217    WifiStateMachine mockWifiStateMachine() {
218        WifiStateMachine stateMachine = mock(WifiStateMachine.class);
219
220        when(stateMachine.isLinkDebouncing()).thenReturn(false);
221        when(stateMachine.isConnected()).thenReturn(false);
222        when(stateMachine.isDisconnected()).thenReturn(true);
223        when(stateMachine.isSupplicantTransientState()).thenReturn(false);
224
225        return stateMachine;
226    }
227
228    WifiNetworkSelector mockWifiNetworkSelector() {
229        WifiNetworkSelector ns = mock(WifiNetworkSelector.class);
230
231        WifiConfiguration candidate = generateWifiConfig(
232                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
233        candidate.BSSID = WifiStateMachine.SUPPLICANT_BSSID_ANY;
234        ScanResult candidateScanResult = new ScanResult();
235        candidateScanResult.SSID = CANDIDATE_SSID;
236        candidateScanResult.BSSID = CANDIDATE_BSSID;
237        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
238
239        when(ns.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
240                anyBoolean(), anyBoolean())).thenReturn(candidate);
241        return ns;
242    }
243
244    WifiInfo getWifiInfo() {
245        WifiInfo wifiInfo = new WifiInfo();
246
247        wifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
248        wifiInfo.setBSSID(null);
249        wifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
250
251        return wifiInfo;
252    }
253
254    WifiConfigManager mockWifiConfigManager() {
255        WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
256
257        when(wifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
258
259        // Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
260        PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
261        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
262        pnoNetworkList.add(pnoNetwork);
263        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
264        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
265
266        return wifiConfigManager;
267    }
268
269    WifiConnectivityManager createConnectivityManager() {
270        return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
271                mWifiConfigManager, mWifiInfo, mWifiNS, mWifiConnectivityHelper,
272                mWifiLastResortWatchdog, mWifiMetrics, mLooper.getLooper(), mClock, true,
273                mFrameworkFacade, null, null, null);
274    }
275
276    /**
277     *  Wifi enters disconnected 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 enterWifiDisconnectedStateWhenScreenOn() {
285        // Set screen to on
286        mWifiConnectivityManager.handleScreenStateChanged(true);
287
288        // Set WiFi to disconnected state
289        mWifiConnectivityManager.handleConnectionStateChanged(
290                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
291
292        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
293    }
294
295    /**
296     *  Wifi enters connected state while screen is on.
297     *
298     * Expected behavior: WifiConnectivityManager calls
299     * WifiStateMachine.startConnectToNetwork() with the
300     * expected candidate network ID and BSSID.
301     */
302    @Test
303    public void enterWifiConnectedStateWhenScreenOn() {
304        // Set screen to on
305        mWifiConnectivityManager.handleScreenStateChanged(true);
306
307        // Set WiFi to connected state
308        mWifiConnectivityManager.handleConnectionStateChanged(
309                WifiConnectivityManager.WIFI_STATE_CONNECTED);
310
311        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
312    }
313
314    /**
315     *  Screen turned on while WiFi in disconnected state.
316     *
317     * Expected behavior: WifiConnectivityManager calls
318     * WifiStateMachine.startConnectToNetwork() with the
319     * expected candidate network ID and BSSID.
320     */
321    @Test
322    public void turnScreenOnWhenWifiInDisconnectedState() {
323        // Set WiFi to disconnected state
324        mWifiConnectivityManager.handleConnectionStateChanged(
325                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
326
327        // Set screen to on
328        mWifiConnectivityManager.handleScreenStateChanged(true);
329
330        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
331                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
332    }
333
334    /**
335     *  Screen turned on while WiFi in connected state.
336     *
337     * Expected behavior: WifiConnectivityManager calls
338     * WifiStateMachine.startConnectToNetwork() with the
339     * expected candidate network ID and BSSID.
340     */
341    @Test
342    public void turnScreenOnWhenWifiInConnectedState() {
343        // Set WiFi to connected state
344        mWifiConnectivityManager.handleConnectionStateChanged(
345                WifiConnectivityManager.WIFI_STATE_CONNECTED);
346
347        // Set screen to on
348        mWifiConnectivityManager.handleScreenStateChanged(true);
349
350        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
351                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
352    }
353
354    /**
355     *  Screen turned on while WiFi in connected state but
356     *  auto roaming is disabled.
357     *
358     * Expected behavior: WifiConnectivityManager doesn't invoke
359     * WifiStateMachine.startConnectToNetwork() because roaming
360     * is turned off.
361     */
362    @Test
363    public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() {
364        // Turn off auto roaming
365        when(mResource.getBoolean(
366                R.bool.config_wifi_framework_enable_associated_network_selection))
367                .thenReturn(false);
368        mWifiConnectivityManager = createConnectivityManager();
369
370        // Set WiFi to connected state
371        mWifiConnectivityManager.handleConnectionStateChanged(
372                WifiConnectivityManager.WIFI_STATE_CONNECTED);
373
374        // Set screen to on
375        mWifiConnectivityManager.handleScreenStateChanged(true);
376
377        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
378                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
379    }
380
381    /**
382     * Multiple back to back connection attempts within the rate interval should be rate limited.
383     *
384     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
385     * with the expected candidate network ID and BSSID for only the expected number of times within
386     * the given interval.
387     */
388    @Test
389    public void connectionAttemptRateLimitedWhenScreenOff() {
390        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
391        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
392        int numAttempts = 0;
393        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
394
395        mWifiConnectivityManager.handleScreenStateChanged(false);
396
397        // First attempt the max rate number of connections within the rate interval.
398        long currentTimeStamp = 0;
399        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
400            currentTimeStamp += connectionAttemptIntervals;
401            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
402            // Set WiFi to disconnected state to trigger PNO scan
403            mWifiConnectivityManager.handleConnectionStateChanged(
404                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
405            numAttempts++;
406        }
407        // Now trigger another connection attempt before the rate interval, this should be
408        // skipped because we've crossed rate limit.
409        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
410        // Set WiFi to disconnected state to trigger PNO scan
411        mWifiConnectivityManager.handleConnectionStateChanged(
412                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
413
414        // Verify that we attempt to connect upto the rate.
415        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
416                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
417    }
418
419    /**
420     * Multiple back to back connection attempts outside the rate interval should not be rate
421     * limited.
422     *
423     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
424     * with the expected candidate network ID and BSSID for only the expected number of times within
425     * the given interval.
426     */
427    @Test
428    public void connectionAttemptNotRateLimitedWhenScreenOff() {
429        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
430        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
431        int numAttempts = 0;
432        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
433
434        mWifiConnectivityManager.handleScreenStateChanged(false);
435
436        // First attempt the max rate number of connections within the rate interval.
437        long currentTimeStamp = 0;
438        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
439            currentTimeStamp += connectionAttemptIntervals;
440            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
441            // Set WiFi to disconnected state to trigger PNO scan
442            mWifiConnectivityManager.handleConnectionStateChanged(
443                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
444            numAttempts++;
445        }
446        // Now trigger another connection attempt after the rate interval, this should not be
447        // skipped because we should've evicted the older attempt.
448        when(mClock.getElapsedSinceBootMillis()).thenReturn(
449                currentTimeStamp + connectionAttemptIntervals * 2);
450        // Set WiFi to disconnected state to trigger PNO scan
451        mWifiConnectivityManager.handleConnectionStateChanged(
452                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
453        numAttempts++;
454
455        // Verify that all the connection attempts went through
456        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
457                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
458    }
459
460    /**
461     * Multiple back to back connection attempts after a user selection should not be rate limited.
462     *
463     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
464     * with the expected candidate network ID and BSSID for only the expected number of times within
465     * the given interval.
466     */
467    @Test
468    public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() {
469        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
470        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
471        int numAttempts = 0;
472        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
473
474        mWifiConnectivityManager.handleScreenStateChanged(false);
475
476        // First attempt the max rate number of connections within the rate interval.
477        long currentTimeStamp = 0;
478        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
479            currentTimeStamp += connectionAttemptIntervals;
480            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
481            // Set WiFi to disconnected state to trigger PNO scan
482            mWifiConnectivityManager.handleConnectionStateChanged(
483                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
484            numAttempts++;
485        }
486
487        mWifiConnectivityManager.setUserConnectChoice(CANDIDATE_NETWORK_ID);
488
489        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
490            currentTimeStamp += connectionAttemptIntervals;
491            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
492            // Set WiFi to disconnected state to trigger PNO scan
493            mWifiConnectivityManager.handleConnectionStateChanged(
494                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
495            numAttempts++;
496        }
497
498        // Verify that all the connection attempts went through
499        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
500                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
501    }
502
503    /**
504     *  PNO retry for low RSSI networks.
505     *
506     * Expected behavior: WifiConnectivityManager doubles the low RSSI
507     * network retry delay value after QNS skips the PNO scan results
508     * because of their low RSSI values.
509     */
510    @Test
511    @Ignore("b/32977707")
512    public void pnoRetryForLowRssiNetwork() {
513        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
514                anyBoolean(), anyBoolean())).thenReturn(null);
515
516        // Set screen to off
517        mWifiConnectivityManager.handleScreenStateChanged(false);
518
519        // Get the current retry delay value
520        int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager
521                .getLowRssiNetworkRetryDelay();
522
523        // Set WiFi to disconnected state to trigger PNO scan
524        mWifiConnectivityManager.handleConnectionStateChanged(
525                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
526
527        // Get the retry delay value after QNS didn't select a
528        // network candicate from the PNO scan results.
529        int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager
530                .getLowRssiNetworkRetryDelay();
531
532        assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
533                lowRssiNetworkRetryDelayAfterPnoValue);
534    }
535
536    /**
537     * Ensure that the watchdog bite increments the "Pno bad" metric.
538     *
539     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
540     * a candidate while watchdog single scan did.
541     */
542    @Test
543    @Ignore("b/32977707")
544    public void watchdogBitePnoBadIncrementsMetrics() {
545        // Set screen to off
546        mWifiConnectivityManager.handleScreenStateChanged(false);
547
548        // Set WiFi to disconnected state to trigger PNO scan
549        mWifiConnectivityManager.handleConnectionStateChanged(
550                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
551
552        // Now fire the watchdog alarm and verify the metrics were incremented.
553        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
554        mLooper.dispatchAll();
555
556        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoBad();
557        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoGood();
558    }
559
560    /**
561     * Ensure that the watchdog bite increments the "Pno good" metric.
562     *
563     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
564     * a candidate which was the same with watchdog single scan.
565     */
566    @Test
567    @Ignore("b/32977707")
568    public void watchdogBitePnoGoodIncrementsMetrics() {
569        // Qns returns no candidate after watchdog single scan.
570        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
571                anyBoolean(), anyBoolean())).thenReturn(null);
572
573        // Set screen to off
574        mWifiConnectivityManager.handleScreenStateChanged(false);
575
576        // Set WiFi to disconnected state to trigger PNO scan
577        mWifiConnectivityManager.handleConnectionStateChanged(
578                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
579
580        // Now fire the watchdog alarm and verify the metrics were incremented.
581        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
582        mLooper.dispatchAll();
583
584        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoGood();
585        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoBad();
586    }
587
588    /**
589     *  Verify that scan interval for screen on and wifi disconnected scenario
590     *  is in the exponential backoff fashion.
591     *
592     * Expected behavior: WifiConnectivityManager doubles periodic
593     * scan interval.
594     */
595    @Test
596    public void checkPeriodicScanIntervalWhenDisconnected() {
597        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
598        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
599
600        // Set screen to ON
601        mWifiConnectivityManager.handleScreenStateChanged(true);
602
603        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
604        // by screen state change can settle
605        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
606        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
607
608        // Set WiFi to disconnected state to trigger periodic scan
609        mWifiConnectivityManager.handleConnectionStateChanged(
610                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
611
612        // Get the first periodic scan interval
613        long firstIntervalMs = mAlarmManager
614                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
615                - currentTimeStamp;
616        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
617
618        currentTimeStamp += firstIntervalMs;
619        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
620
621        // Now fire the first periodic scan alarm timer
622        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
623        mLooper.dispatchAll();
624
625        // Get the second periodic scan interval
626        long secondIntervalMs = mAlarmManager
627                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
628                - currentTimeStamp;
629
630        // Verify the intervals are exponential back off
631        assertEquals(firstIntervalMs * 2, secondIntervalMs);
632
633        currentTimeStamp += secondIntervalMs;
634        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
635
636        // Make sure we eventually stay at the maximum scan interval.
637        long intervalMs = 0;
638        for (int i = 0; i < 5; i++) {
639            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
640            mLooper.dispatchAll();
641            intervalMs = mAlarmManager
642                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
643                    - currentTimeStamp;
644            currentTimeStamp += intervalMs;
645            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
646        }
647
648        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
649    }
650
651    /**
652     *  Verify that scan interval for screen on and wifi connected scenario
653     *  is in the exponential backoff fashion.
654     *
655     * Expected behavior: WifiConnectivityManager doubles periodic
656     * scan interval.
657     */
658    @Test
659    public void checkPeriodicScanIntervalWhenConnected() {
660        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
661        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
662
663        // Set screen to ON
664        mWifiConnectivityManager.handleScreenStateChanged(true);
665
666        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
667        // by screen state change can settle
668        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
669        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
670
671        // Set WiFi to connected state to trigger periodic scan
672        mWifiConnectivityManager.handleConnectionStateChanged(
673                WifiConnectivityManager.WIFI_STATE_CONNECTED);
674
675        // Get the first periodic scan interval
676        long firstIntervalMs = mAlarmManager
677                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
678                - currentTimeStamp;
679        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
680
681        currentTimeStamp += firstIntervalMs;
682        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
683
684        // Now fire the first periodic scan alarm timer
685        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
686        mLooper.dispatchAll();
687
688        // Get the second periodic scan interval
689        long secondIntervalMs = mAlarmManager
690                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
691                - currentTimeStamp;
692
693        // Verify the intervals are exponential back off
694        assertEquals(firstIntervalMs * 2, secondIntervalMs);
695
696        currentTimeStamp += secondIntervalMs;
697        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
698
699        // Make sure we eventually stay at the maximum scan interval.
700        long intervalMs = 0;
701        for (int i = 0; i < 5; i++) {
702            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
703            mLooper.dispatchAll();
704            intervalMs = mAlarmManager
705                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
706                    - currentTimeStamp;
707            currentTimeStamp += intervalMs;
708            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
709        }
710
711        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
712    }
713
714    /**
715     *  When screen on trigger a disconnected state change event then a connected state
716     *  change event back to back to verify that the minium scan interval is enforced.
717     *
718     * Expected behavior: WifiConnectivityManager start the second periodic single
719     * scan PERIODIC_SCAN_INTERVAL_MS after the first one.
720     */
721    @Test
722    public void checkMinimumPeriodicScanIntervalWhenScreenOnAndConnected() {
723        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
724        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
725
726        // Set screen to ON
727        mWifiConnectivityManager.handleScreenStateChanged(true);
728
729        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
730        // by screen state change can settle
731        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
732        long scanForDisconnectedTimeStamp = currentTimeStamp;
733        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
734
735        // Set WiFi to disconnected state which triggers a scan immediately
736        mWifiConnectivityManager.handleConnectionStateChanged(
737                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
738        verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
739
740        // Set up time stamp for when entering CONNECTED state
741        currentTimeStamp += 2000;
742        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
743
744        // Set WiFi to connected state to trigger its periodic scan
745        mWifiConnectivityManager.handleConnectionStateChanged(
746                WifiConnectivityManager.WIFI_STATE_CONNECTED);
747
748        // The very first scan triggered for connected state is actually via the alarm timer
749        // and it obeys the minimum scan interval
750        long firstScanForConnectedTimeStamp = mAlarmManager
751                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
752
753        // Verify that the first scan for connected state is scheduled PERIODIC_SCAN_INTERVAL_MS
754        // after the scan for disconnected state
755        assertEquals(firstScanForConnectedTimeStamp, scanForDisconnectedTimeStamp
756                + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
757    }
758
759    /**
760     *  When screen on trigger a connected state change event then a disconnected state
761     *  change event back to back to verify that a scan is fired immediately for the
762     *  disconnected state change event.
763     *
764     * Expected behavior: WifiConnectivityManager directly starts the periodic immediately
765     * for the disconnected state change event. The second scan for disconnected state is
766     * via alarm timer.
767     */
768    @Test
769    public void scanImmediatelyWhenScreenOnAndDisconnected() {
770        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
771        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
772
773        // Set screen to ON
774        mWifiConnectivityManager.handleScreenStateChanged(true);
775
776        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
777        // by screen state change can settle
778        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
779        long scanForConnectedTimeStamp = currentTimeStamp;
780        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
781
782        // Set WiFi to connected state to trigger the periodic scan
783        mWifiConnectivityManager.handleConnectionStateChanged(
784                WifiConnectivityManager.WIFI_STATE_CONNECTED);
785        verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
786
787        // Set up the time stamp for when entering DISCONNECTED state
788        currentTimeStamp += 2000;
789        long enteringDisconnectedStateTimeStamp = currentTimeStamp;
790        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
791
792        // Set WiFi to disconnected state to trigger its periodic scan
793        mWifiConnectivityManager.handleConnectionStateChanged(
794                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
795
796        // Verify the very first scan for DISCONNECTED state is fired immediately
797        verify(mWifiScanner, times(2)).startScan(anyObject(), anyObject(), anyObject());
798        long secondScanForDisconnectedTimeStamp = mAlarmManager
799                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
800
801        // Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after
802        // entering DISCONNECTED state.
803        assertEquals(secondScanForDisconnectedTimeStamp, enteringDisconnectedStateTimeStamp
804                + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
805    }
806
807    /**
808     *  When screen on trigger a connection state change event and a forced connectivity
809     *  scan event back to back to verify that the minimum scan interval is not applied
810     *  in this scenario.
811     *
812     * Expected behavior: WifiConnectivityManager starts the second periodic single
813     * scan immediately.
814     */
815    @Test
816    public void checkMinimumPeriodicScanIntervalNotEnforced() {
817        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
818        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
819
820        // Set screen to ON
821        mWifiConnectivityManager.handleScreenStateChanged(true);
822
823        // Wait for MAX_PERIODIC_SCAN_INTERVAL_MS so that any impact triggered
824        // by screen state change can settle
825        currentTimeStamp += WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS;
826        long firstScanTimeStamp = currentTimeStamp;
827        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
828
829        // Set WiFi to connected state to trigger the periodic scan
830        mWifiConnectivityManager.handleConnectionStateChanged(
831                WifiConnectivityManager.WIFI_STATE_CONNECTED);
832
833        // Set the second scan attempt time stamp
834        currentTimeStamp += 2000;
835        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
836
837        // Allow untrusted networks so WifiConnectivityManager starts a periodic scan
838        // immediately.
839        mWifiConnectivityManager.setUntrustedConnectionAllowed(true);
840
841        // Get the second periodic scan actual time stamp. Note, this scan is not
842        // started from the AlarmManager.
843        long secondScanTimeStamp = mWifiConnectivityManager.getLastPeriodicSingleScanTimeStamp();
844
845        // Verify that the second scan is fired immediately
846        assertEquals(secondScanTimeStamp, currentTimeStamp);
847    }
848
849    /**
850     * Verify that we perform full band scan when the currently connected network's tx/rx success
851     * rate is low.
852     *
853     * Expected behavior: WifiConnectivityManager does full band scan.
854     */
855    @Test
856    public void checkSingleScanSettingsWhenConnectedWithLowDataRate() {
857        mWifiInfo.txSuccessRate = 0;
858        mWifiInfo.rxSuccessRate = 0;
859
860        final HashSet<Integer> channelList = new HashSet<>();
861        channelList.add(1);
862        channelList.add(2);
863        channelList.add(3);
864
865        when(mWifiStateMachine.getCurrentWifiConfiguration())
866                .thenReturn(new WifiConfiguration());
867        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
868                anyInt())).thenReturn(channelList);
869
870        doAnswer(new AnswerWithArguments() {
871            public void answer(ScanSettings settings, ScanListener listener,
872                    WorkSource workSource) throws Exception {
873                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
874                assertNull(settings.channels);
875            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
876
877        // Set screen to ON
878        mWifiConnectivityManager.handleScreenStateChanged(true);
879
880        // Set WiFi to connected state to trigger periodic scan
881        mWifiConnectivityManager.handleConnectionStateChanged(
882                WifiConnectivityManager.WIFI_STATE_CONNECTED);
883
884        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
885    }
886
887    /**
888     * Verify that we perform partial scan when the currently connected network's tx/rx success
889     * rate is high and when the currently connected network is present in scan
890     * cache in WifiConfigManager.
891     *
892     * Expected behavior: WifiConnectivityManager does full band scan.
893     */
894    @Test
895    public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
896        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
897        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
898
899        final HashSet<Integer> channelList = new HashSet<>();
900        channelList.add(1);
901        channelList.add(2);
902        channelList.add(3);
903
904        when(mWifiStateMachine.getCurrentWifiConfiguration())
905                .thenReturn(new WifiConfiguration());
906        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
907                anyInt())).thenReturn(channelList);
908
909        doAnswer(new AnswerWithArguments() {
910            public void answer(ScanSettings settings, ScanListener listener,
911                    WorkSource workSource) throws Exception {
912                assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED);
913                assertEquals(settings.channels.length, channelList.size());
914                for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) {
915                    assertTrue(channelList.contains(settings.channels[chanIdx].frequency));
916                }
917            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
918
919        // Set screen to ON
920        mWifiConnectivityManager.handleScreenStateChanged(true);
921
922        // Set WiFi to connected state to trigger periodic scan
923        mWifiConnectivityManager.handleConnectionStateChanged(
924                WifiConnectivityManager.WIFI_STATE_CONNECTED);
925
926        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
927    }
928
929    /**
930     * Verify that we fall back to full band scan when the currently connected network's tx/rx
931     * success rate is high and the currently connected network is not present in scan cache in
932     * WifiConfigManager. This is simulated by returning an empty hashset in |makeChannelList|.
933     *
934     * Expected behavior: WifiConnectivityManager does full band scan.
935     */
936    @Test
937    public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
938        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
939        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
940
941        final HashSet<Integer> channelList = new HashSet<>();
942
943        when(mWifiStateMachine.getCurrentWifiConfiguration())
944                .thenReturn(new WifiConfiguration());
945        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
946                anyInt())).thenReturn(channelList);
947
948        doAnswer(new AnswerWithArguments() {
949            public void answer(ScanSettings settings, ScanListener listener,
950                    WorkSource workSource) throws Exception {
951                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
952                assertNull(settings.channels);
953            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
954
955        // Set screen to ON
956        mWifiConnectivityManager.handleScreenStateChanged(true);
957
958        // Set WiFi to connected state to trigger periodic scan
959        mWifiConnectivityManager.handleConnectionStateChanged(
960                WifiConnectivityManager.WIFI_STATE_CONNECTED);
961
962        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
963    }
964
965    /**
966     *  Verify that we retry connectivity scan up to MAX_SCAN_RESTART_ALLOWED times
967     *  when Wifi somehow gets into a bad state and fails to scan.
968     *
969     * Expected behavior: WifiConnectivityManager schedules connectivity scan
970     * MAX_SCAN_RESTART_ALLOWED times.
971     */
972    @Test
973    public void checkMaximumScanRetry() {
974        // Set screen to ON
975        mWifiConnectivityManager.handleScreenStateChanged(true);
976
977        doAnswer(new AnswerWithArguments() {
978            public void answer(ScanSettings settings, ScanListener listener,
979                    WorkSource workSource) throws Exception {
980                listener.onFailure(-1, "ScanFailure");
981            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
982
983        // Set WiFi to disconnected state to trigger the single scan based periodic scan
984        mWifiConnectivityManager.handleConnectionStateChanged(
985                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
986
987        // Fire the alarm timer 2x timers
988        for (int i = 0; i < (WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED * 2); i++) {
989            mAlarmManager.dispatch(WifiConnectivityManager.RESTART_SINGLE_SCAN_TIMER_TAG);
990            mLooper.dispatchAll();
991        }
992
993        // Verify that the connectivity scan has been retried for MAX_SCAN_RESTART_ALLOWED
994        // times. Note, WifiScanner.startScan() is invoked MAX_SCAN_RESTART_ALLOWED + 1 times.
995        // The very first scan is the initial one, and the other MAX_SCAN_RESTART_ALLOWED
996        // are the retrial ones.
997        verify(mWifiScanner, times(WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED + 1)).startScan(
998                anyObject(), anyObject(), anyObject());
999    }
1000
1001    /**
1002     * Listen to scan results not requested by WifiConnectivityManager and
1003     * act on them.
1004     *
1005     * Expected behavior: WifiConnectivityManager calls
1006     * WifiStateMachine.startConnectToNetwork() with the
1007     * expected candidate network ID and BSSID.
1008     */
1009    @Test
1010    public void listenToAllSingleScanResults() {
1011        ScanSettings settings = new ScanSettings();
1012        ScanListener scanListener = mock(ScanListener.class);
1013
1014        // Request a single scan outside of WifiConnectivityManager.
1015        mWifiScanner.startScan(settings, scanListener, WIFI_WORK_SOURCE);
1016
1017        // Verify that WCM receives the scan results and initiates a connection
1018        // to the network.
1019        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1020    }
1021
1022    /**
1023     *  Verify that a forced connectivity scan waits for full band scan
1024     *  results.
1025     *
1026     * Expected behavior: WifiConnectivityManager doesn't invoke
1027     * WifiStateMachine.startConnectToNetwork() when full band scan
1028     * results are not available.
1029     */
1030    @Test
1031    public void waitForFullBandScanResults() {
1032        // Set WiFi to connected state.
1033        mWifiConnectivityManager.handleConnectionStateChanged(
1034                WifiConnectivityManager.WIFI_STATE_CONNECTED);
1035
1036        // Set up as partial scan results.
1037        when(mScanData.isAllChannelsScanned()).thenReturn(false);
1038
1039        // Force a connectivity scan which enables WifiConnectivityManager
1040        // to wait for full band scan results.
1041        mWifiConnectivityManager.forceConnectivityScan();
1042
1043        // No roaming because no full band scan results.
1044        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
1045                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1046
1047        // Set up as full band scan results.
1048        when(mScanData.isAllChannelsScanned()).thenReturn(true);
1049
1050        // Force a connectivity scan which enables WifiConnectivityManager
1051        // to wait for full band scan results.
1052        mWifiConnectivityManager.forceConnectivityScan();
1053
1054        // Roaming attempt because full band scan results are available.
1055        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1056    }
1057
1058    /**
1059     *  Verify the BSSID blacklist implementation.
1060     *
1061     * Expected behavior: A BSSID gets blacklisted after being disabled
1062     * for 3 times, and becomes available after being re-enabled.
1063     */
1064    @Test
1065    public void blacklistAndReenableBssid() {
1066        String bssid = "6c:f3:7f:ae:8c:f3";
1067
1068        // Verify that a BSSID gets blacklisted only after being disabled
1069        // for BSSID_BLACKLIST_THRESHOLD times for reasons other than
1070        // REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA.
1071        for (int i = 0; i < WifiConnectivityManager.BSSID_BLACKLIST_THRESHOLD; i++) {
1072            assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
1073            mWifiConnectivityManager.trackBssid(bssid, false, 1);
1074        }
1075
1076        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
1077
1078        // Re-enable the bssid.
1079        mWifiConnectivityManager.trackBssid(bssid, true, 1);
1080
1081        // The bssid should no longer be blacklisted.
1082        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
1083    }
1084
1085    /**
1086     *  Verify that a network gets blacklisted immediately if it is unable
1087     *  to handle new stations.
1088     */
1089    @Test
1090    public void blacklistNetworkImmediatelyIfApHasNoCapacityForNewStation() {
1091        String bssid = "6c:f3:7f:ae:8c:f3";
1092
1093        mWifiConnectivityManager.trackBssid(bssid, false,
1094                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
1095
1096        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
1097    }
1098
1099    /**
1100     *  Verify that a blacklisted BSSID becomes available only after
1101     *  BSSID_BLACKLIST_EXPIRE_TIME_MS.
1102     */
1103    @Test
1104    public void verifyBlacklistRefreshedAfterScanResults() {
1105        String bssid = "6c:f3:7f:ae:8c:f3";
1106
1107        mWifiConnectivityManager.trackBssid(bssid, false,
1108                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
1109        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
1110
1111        // Force a connectivity scan in less than BSSID_BLACKLIST_EXPIRE_TIME_MS.
1112        // Arrival of scan results will trigger WifiConnectivityManager to refresh its
1113        // BSSID blacklist. Verify that the blacklisted BSSId is not freed because
1114        // its blacklist expiration time hasn't reached yet.
1115        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
1116                + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS / 2);
1117        mWifiConnectivityManager.forceConnectivityScan();
1118        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
1119
1120        // Force another connectivity scan at BSSID_BLACKLIST_EXPIRE_TIME_MS from when the
1121        // BSSID was blacklisted. Verify that the blacklisted BSSId is freed.
1122        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
1123                + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS);
1124        mWifiConnectivityManager.forceConnectivityScan();
1125        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
1126    }
1127
1128    /**
1129     * When WifiConnectivityManager is on and Wifi client mode is enabled, framework
1130     * queries firmware via WifiConnectivityHelper to check if firmware roaming is
1131     * supported and its capability.
1132     *
1133     * Expected behavior: WifiConnectivityManager#setWifiEnabled calls into
1134     * WifiConnectivityHelper#getFirmwareRoamingInfo
1135     */
1136    @Test
1137    public void verifyGetFirmwareRoamingInfoIsCalledWhenEnableWiFiAndWcmOn() {
1138        reset(mWifiConnectivityHelper);
1139        // WifiConnectivityManager is on by default
1140        mWifiConnectivityManager.setWifiEnabled(true);
1141        verify(mWifiConnectivityHelper).getFirmwareRoamingInfo();
1142    }
1143
1144    /**
1145     * When WifiConnectivityManager is off,  verify that framework does not
1146     * query firmware via WifiConnectivityHelper to check if firmware roaming is
1147     * supported and its capability when enabling Wifi client mode.
1148     *
1149     * Expected behavior: WifiConnectivityManager#setWifiEnabled does not call into
1150     * WifiConnectivityHelper#getFirmwareRoamingInfo
1151     */
1152    @Test
1153    public void verifyGetFirmwareRoamingInfoIsNotCalledWhenEnableWiFiAndWcmOff() {
1154        reset(mWifiConnectivityHelper);
1155        mWifiConnectivityManager.enable(false);
1156        mWifiConnectivityManager.setWifiEnabled(true);
1157        verify(mWifiConnectivityHelper, times(0)).getFirmwareRoamingInfo();
1158    }
1159
1160    /*
1161     * Firmware supports controlled roaming.
1162     * Connect to a network which doesn't have a config specified BSSID.
1163     *
1164     * Expected behavior: WifiConnectivityManager calls
1165     * WifiStateMachine.startConnectToNetwork() with the
1166     * expected candidate network ID, and the BSSID value should be
1167     * 'any' since firmware controls the roaming.
1168     */
1169    @Test
1170    public void useAnyBssidToConnectWhenFirmwareRoamingOnAndConfigHasNoBssidSpecified() {
1171        // Firmware controls roaming
1172        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
1173
1174        // Set screen to on
1175        mWifiConnectivityManager.handleScreenStateChanged(true);
1176
1177        // Set WiFi to disconnected state
1178        mWifiConnectivityManager.handleConnectionStateChanged(
1179                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
1180
1181        verify(mWifiStateMachine).startConnectToNetwork(
1182                CANDIDATE_NETWORK_ID, WifiStateMachine.SUPPLICANT_BSSID_ANY);
1183    }
1184
1185    /*
1186     * Firmware supports controlled roaming.
1187     * Connect to a network which has a config specified BSSID.
1188     *
1189     * Expected behavior: WifiConnectivityManager calls
1190     * WifiStateMachine.startConnectToNetwork() with the
1191     * expected candidate network ID, and the BSSID value should be
1192     * the config specified one.
1193     */
1194    @Test
1195    public void useConfigSpecifiedBssidToConnectWhenFirmwareRoamingOn() {
1196        // Firmware controls roaming
1197        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
1198
1199        // Set up the candidate configuration such that it has a BSSID specified.
1200        WifiConfiguration candidate = generateWifiConfig(
1201                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1202        candidate.BSSID = CANDIDATE_BSSID; // config specified
1203        ScanResult candidateScanResult = new ScanResult();
1204        candidateScanResult.SSID = CANDIDATE_SSID;
1205        candidateScanResult.BSSID = CANDIDATE_BSSID;
1206        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
1207
1208        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
1209                anyBoolean(), anyBoolean())).thenReturn(candidate);
1210
1211        // Set screen to on
1212        mWifiConnectivityManager.handleScreenStateChanged(true);
1213
1214        // Set WiFi to disconnected state
1215        mWifiConnectivityManager.handleConnectionStateChanged(
1216                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
1217
1218        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1219    }
1220
1221    /*
1222     * Firmware does not support controlled roaming.
1223     * Connect to a network which doesn't have a config specified BSSID.
1224     *
1225     * Expected behavior: WifiConnectivityManager calls
1226     * WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
1227     * and the BSSID value should be the candidate scan result specified.
1228     */
1229    @Test
1230    public void useScanResultBssidToConnectWhenFirmwareRoamingOffAndConfigHasNoBssidSpecified() {
1231        // Set screen to on
1232        mWifiConnectivityManager.handleScreenStateChanged(true);
1233
1234        // Set WiFi to disconnected state
1235        mWifiConnectivityManager.handleConnectionStateChanged(
1236                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
1237
1238        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1239    }
1240
1241    /*
1242     * Firmware does not support controlled roaming.
1243     * Connect to a network which has a config specified BSSID.
1244     *
1245     * Expected behavior: WifiConnectivityManager calls
1246     * WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
1247     * and the BSSID value should be the config specified one.
1248     */
1249    @Test
1250    public void useConfigSpecifiedBssidToConnectionWhenFirmwareRoamingOff() {
1251        // Set up the candidate configuration such that it has a BSSID specified.
1252        WifiConfiguration candidate = generateWifiConfig(
1253                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1254        candidate.BSSID = CANDIDATE_BSSID; // config specified
1255        ScanResult candidateScanResult = new ScanResult();
1256        candidateScanResult.SSID = CANDIDATE_SSID;
1257        candidateScanResult.BSSID = CANDIDATE_BSSID;
1258        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
1259
1260        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
1261                anyBoolean(), anyBoolean())).thenReturn(candidate);
1262
1263        // Set screen to on
1264        mWifiConnectivityManager.handleScreenStateChanged(true);
1265
1266        // Set WiFi to disconnected state
1267        mWifiConnectivityManager.handleConnectionStateChanged(
1268                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
1269
1270        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1271    }
1272
1273    /**
1274     * Firmware does not support controlled roaming.
1275     * WiFi in connected state, framework triggers roaming.
1276     *
1277     * Expected behavior: WifiConnectivityManager invokes
1278     * WifiStateMachine.startRoamToNetwork().
1279     */
1280    @Test
1281    public void frameworkInitiatedRoaming() {
1282        // Mock the currently connected network which has the same networkID and
1283        // SSID as the one to be selected.
1284        WifiConfiguration currentNetwork = generateWifiConfig(
1285                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1286        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
1287
1288        // Set WiFi to connected state
1289        mWifiConnectivityManager.handleConnectionStateChanged(
1290                WifiConnectivityManager.WIFI_STATE_CONNECTED);
1291
1292        // Set screen to on
1293        mWifiConnectivityManager.handleScreenStateChanged(true);
1294
1295        verify(mWifiStateMachine).startRoamToNetwork(eq(CANDIDATE_NETWORK_ID),
1296                mCandidateScanResultCaptor.capture());
1297        assertEquals(mCandidateScanResultCaptor.getValue().BSSID, CANDIDATE_BSSID);
1298    }
1299
1300    /**
1301     * Firmware supports controlled roaming.
1302     * WiFi in connected state, framework does not trigger roaming
1303     * as it's handed off to the firmware.
1304     *
1305     * Expected behavior: WifiConnectivityManager doesn't invoke
1306     * WifiStateMachine.startRoamToNetwork().
1307     */
1308    @Test
1309    public void noFrameworkRoamingIfConnectedAndFirmwareRoamingSupported() {
1310        // Mock the currently connected network which has the same networkID and
1311        // SSID as the one to be selected.
1312        WifiConfiguration currentNetwork = generateWifiConfig(
1313                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1314        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
1315
1316        // Firmware controls roaming
1317        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
1318
1319        // Set WiFi to connected state
1320        mWifiConnectivityManager.handleConnectionStateChanged(
1321                WifiConnectivityManager.WIFI_STATE_CONNECTED);
1322
1323        // Set screen to on
1324        mWifiConnectivityManager.handleScreenStateChanged(true);
1325
1326        verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
1327    }
1328
1329    /*
1330     * Wifi in disconnected state. Drop the connection attempt if the recommended
1331     * network configuration has a BSSID specified but the scan result BSSID doesn't
1332     * match it.
1333     *
1334     * Expected behavior: WifiConnectivityManager doesn't invoke
1335     * WifiStateMachine.startConnectToNetwork().
1336     */
1337    @Test
1338    public void dropConnectAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
1339        // Set up the candidate configuration such that it has a BSSID specified.
1340        WifiConfiguration candidate = generateWifiConfig(
1341                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1342        candidate.BSSID = CANDIDATE_BSSID; // config specified
1343        ScanResult candidateScanResult = new ScanResult();
1344        candidateScanResult.SSID = CANDIDATE_SSID;
1345        // Set up the scan result BSSID to be different from the config specified one.
1346        candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
1347        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
1348
1349        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
1350                anyBoolean(), anyBoolean())).thenReturn(candidate);
1351
1352        // Set screen to on
1353        mWifiConnectivityManager.handleScreenStateChanged(true);
1354
1355        // Set WiFi to disconnected state
1356        mWifiConnectivityManager.handleConnectionStateChanged(
1357                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
1358
1359        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
1360                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
1361    }
1362
1363    /*
1364     * Wifi in connected state. Drop the roaming attempt if the recommended
1365     * network configuration has a BSSID specified but the scan result BSSID doesn't
1366     * match it.
1367     *
1368     * Expected behavior: WifiConnectivityManager doesn't invoke
1369     * WifiStateMachine.startRoamToNetwork().
1370     */
1371    @Test
1372    public void dropRoamingAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
1373        // Mock the currently connected network which has the same networkID and
1374        // SSID as the one to be selected.
1375        WifiConfiguration currentNetwork = generateWifiConfig(
1376                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1377        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
1378
1379        // Set up the candidate configuration such that it has a BSSID specified.
1380        WifiConfiguration candidate = generateWifiConfig(
1381                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
1382        candidate.BSSID = CANDIDATE_BSSID; // config specified
1383        ScanResult candidateScanResult = new ScanResult();
1384        candidateScanResult.SSID = CANDIDATE_SSID;
1385        // Set up the scan result BSSID to be different from the config specified one.
1386        candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
1387        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
1388
1389        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
1390                anyBoolean(), anyBoolean())).thenReturn(candidate);
1391
1392        // Set WiFi to connected state
1393        mWifiConnectivityManager.handleConnectionStateChanged(
1394                WifiConnectivityManager.WIFI_STATE_CONNECTED);
1395
1396        // Set screen to on
1397        mWifiConnectivityManager.handleScreenStateChanged(true);
1398
1399        verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
1400    }
1401}
1402