WifiConnectivityManagerTest.java revision fb196453c07daad5e525520cecad84cec5d89fb7
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;
20
21import static org.junit.Assert.*;
22import static org.mockito.Mockito.*;
23
24import android.content.Context;
25import android.content.res.Resources;
26import android.net.wifi.ScanResult;
27import android.net.wifi.ScanResult.InformationElement;
28import android.net.wifi.SupplicantState;
29import android.net.wifi.WifiConfiguration;
30import android.net.wifi.WifiInfo;
31import android.net.wifi.WifiManager;
32import android.net.wifi.WifiScanner;
33import android.net.wifi.WifiScanner.PnoScanListener;
34import android.net.wifi.WifiScanner.PnoSettings;
35import android.net.wifi.WifiScanner.ScanListener;
36import android.net.wifi.WifiScanner.ScanSettings;
37import android.net.wifi.WifiSsid;
38import android.os.WorkSource;
39import android.test.suitebuilder.annotation.SmallTest;
40
41import com.android.internal.R;
42import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
43
44import org.junit.After;
45import org.junit.Before;
46import org.junit.Test;
47
48import java.nio.charset.StandardCharsets;
49import java.util.ArrayList;
50import java.util.HashSet;
51import java.util.concurrent.atomic.AtomicInteger;
52
53/**
54 * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
55 */
56@SmallTest
57public class WifiConnectivityManagerTest {
58
59    /**
60     * Called before each test
61     */
62    @Before
63    public void setUp() throws Exception {
64        mWifiInjector = mockWifiInjector();
65        mResource = mockResource();
66        mAlarmManager = new MockAlarmManager();
67        mContext = mockContext();
68        mWifiStateMachine = mockWifiStateMachine();
69        mWifiConfigManager = mockWifiConfigManager();
70        mWifiInfo = getWifiInfo();
71        mWifiScanner = mockWifiScanner();
72        mWifiQNS = mockWifiQualifiedNetworkSelector();
73        mWifiConnectivityManager = new WifiConnectivityManager(mContext, mWifiStateMachine,
74                mWifiScanner, mWifiConfigManager, mWifiInfo, mWifiQNS, mWifiInjector,
75                mLooper.getLooper());
76        mWifiConnectivityManager.setWifiEnabled(true);
77        when(mClock.currentTimeMillis()).thenReturn(System.currentTimeMillis());
78    }
79
80    /**
81     * Called after each test
82     */
83    @After
84    public void cleanup() {
85        validateMockitoUsage();
86    }
87
88    private Resources mResource;
89    private Context mContext;
90    private MockAlarmManager mAlarmManager;
91    private MockLooper mLooper = new MockLooper();
92    private WifiConnectivityManager mWifiConnectivityManager;
93    private WifiQualifiedNetworkSelector mWifiQNS;
94    private WifiStateMachine mWifiStateMachine;
95    private WifiScanner mWifiScanner;
96    private WifiConfigManager mWifiConfigManager;
97    private WifiInfo mWifiInfo;
98    private Clock mClock = mock(Clock.class);
99    private WifiLastResortWatchdog mWifiLastResortWatchdog;
100    private WifiMetrics mWifiMetrics;
101    private WifiInjector mWifiInjector;
102
103    private static final int CANDIDATE_NETWORK_ID = 0;
104    private static final String CANDIDATE_SSID = "\"AnSsid\"";
105    private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
106    private static final String TAG = "WifiConnectivityManager Unit Test";
107    private static final long CURRENT_SYSTEM_TIME_MS = 1000;
108
109    Resources mockResource() {
110        Resources resource = mock(Resources.class);
111
112        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
113        when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
114
115        return resource;
116    }
117
118    Context mockContext() {
119        Context context = mock(Context.class);
120
121        when(context.getResources()).thenReturn(mResource);
122        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
123                mAlarmManager.getAlarmManager());
124
125        return context;
126    }
127
128    WifiScanner mockWifiScanner() {
129        WifiScanner scanner = mock(WifiScanner.class);
130
131        // dummy scan results. QNS PeriodicScanListener bulids scanDetails from
132        // the fullScanResult and doesn't really use results
133        final WifiScanner.ScanData[] scanDatas = new WifiScanner.ScanData[1];
134
135        // do a synchronous answer for the ScanListener callbacks
136        doAnswer(new AnswerWithArguments() {
137                public void answer(ScanSettings settings, ScanListener listener,
138                        WorkSource workSource) throws Exception {
139                    listener.onResults(scanDatas);
140                }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
141
142        doAnswer(new AnswerWithArguments() {
143                public void answer(ScanSettings settings, ScanListener listener,
144                        WorkSource workSource) throws Exception {
145                    listener.onResults(scanDatas);
146                }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
147
148        // This unfortunately needs to be a somewhat valid scan result, otherwise
149        // |ScanDetailUtil.toScanDetail| raises exceptions.
150        final ScanResult[] scanResults = new ScanResult[1];
151        scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
152                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps",
153                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
154        scanResults[0].informationElements = new InformationElement[1];
155        scanResults[0].informationElements[0] = new InformationElement();
156        scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
157        scanResults[0].informationElements[0].bytes =
158                CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
159
160        doAnswer(new AnswerWithArguments() {
161            public void answer(ScanSettings settings, PnoSettings pnoSettings,
162                    PnoScanListener listener) throws Exception {
163                listener.onPnoNetworkFound(scanResults);
164            }}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject());
165
166        doAnswer(new AnswerWithArguments() {
167            public void answer(ScanSettings settings, PnoSettings pnoSettings,
168                    PnoScanListener listener) throws Exception {
169                listener.onPnoNetworkFound(scanResults);
170            }}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject());
171
172        return scanner;
173    }
174
175    WifiStateMachine mockWifiStateMachine() {
176        WifiStateMachine stateMachine = mock(WifiStateMachine.class);
177
178        when(stateMachine.getFrequencyBand()).thenReturn(1);
179        when(stateMachine.isLinkDebouncing()).thenReturn(false);
180        when(stateMachine.isConnected()).thenReturn(false);
181        when(stateMachine.isDisconnected()).thenReturn(true);
182        when(stateMachine.isSupplicantTransientState()).thenReturn(false);
183
184        return stateMachine;
185    }
186
187    WifiQualifiedNetworkSelector mockWifiQualifiedNetworkSelector() {
188        WifiQualifiedNetworkSelector qns = mock(WifiQualifiedNetworkSelector.class);
189
190        WifiConfiguration candidate = generateWifiConfig(
191                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
192        candidate.BSSID = CANDIDATE_BSSID;
193        ScanResult candidateScanResult = new ScanResult();
194        candidateScanResult.SSID = CANDIDATE_SSID;
195        candidateScanResult.BSSID = CANDIDATE_BSSID;
196        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
197
198        when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
199              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(candidate);
200        return qns;
201    }
202
203    WifiInfo getWifiInfo() {
204        WifiInfo wifiInfo = new WifiInfo();
205
206        wifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
207        wifiInfo.setBSSID(null);
208        wifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
209
210        return wifiInfo;
211    }
212
213    WifiConfigManager mockWifiConfigManager() {
214        WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
215
216        when(wifiConfigManager.getWifiConfiguration(anyInt())).thenReturn(null);
217        wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger(
218                WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND);
219        wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger(
220                WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD);
221
222        // Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
223        PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
224        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
225        pnoNetworkList.add(pnoNetwork);
226        when(wifiConfigManager.retrieveDisconnectedPnoNetworkList()).thenReturn(pnoNetworkList);
227        when(wifiConfigManager.retrieveConnectedPnoNetworkList()).thenReturn(pnoNetworkList);
228
229        return wifiConfigManager;
230    }
231
232    WifiInjector mockWifiInjector() {
233        WifiInjector wifiInjector = mock(WifiInjector.class);
234        mWifiLastResortWatchdog = mock(WifiLastResortWatchdog.class);
235        mWifiMetrics = mock(WifiMetrics.class);
236        when(wifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
237        when(wifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
238        when(wifiInjector.getClock()).thenReturn(mClock);
239        return wifiInjector;
240    }
241
242    /**
243     *  Wifi enters disconnected state while screen is on.
244     *
245     * Expected behavior: WifiConnectivityManager calls
246     * WifiStateMachine.autoConnectToNetwork() with the
247     * expected candidate network ID and BSSID.
248     */
249    @Test
250    public void enterWifiDisconnectedStateWhenScreenOn() {
251        // Set screen to on
252        mWifiConnectivityManager.handleScreenStateChanged(true);
253
254        // Set WiFi to disconnected state
255        mWifiConnectivityManager.handleConnectionStateChanged(
256                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
257
258        verify(mWifiStateMachine).autoConnectToNetwork(
259                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
260    }
261
262    /**
263     *  Wifi enters connected state while screen is on.
264     *
265     * Expected behavior: WifiConnectivityManager calls
266     * WifiStateMachine.autoConnectToNetwork() with the
267     * expected candidate network ID and BSSID.
268     */
269    @Test
270    public void enterWifiConnectedStateWhenScreenOn() {
271        // Set screen to on
272        mWifiConnectivityManager.handleScreenStateChanged(true);
273
274        // Set WiFi to connected state
275        mWifiConnectivityManager.handleConnectionStateChanged(
276                WifiConnectivityManager.WIFI_STATE_CONNECTED);
277
278        verify(mWifiStateMachine).autoConnectToNetwork(
279                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
280    }
281
282    /**
283     *  Screen turned on while WiFi in disconnected state.
284     *
285     * Expected behavior: WifiConnectivityManager calls
286     * WifiStateMachine.autoConnectToNetwork() with the
287     * expected candidate network ID and BSSID.
288     */
289    @Test
290    public void turnScreenOnWhenWifiInDisconnectedState() {
291        // Set WiFi to disconnected state
292        mWifiConnectivityManager.handleConnectionStateChanged(
293                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
294
295        // Set screen to on
296        mWifiConnectivityManager.handleScreenStateChanged(true);
297
298        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
299                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
300    }
301
302    /**
303     *  Screen turned on while WiFi in connected state.
304     *
305     * Expected behavior: WifiConnectivityManager calls
306     * WifiStateMachine.autoConnectToNetwork() with the
307     * expected candidate network ID and BSSID.
308     */
309    @Test
310    public void turnScreenOnWhenWifiInConnectedState() {
311        // Set WiFi to connected state
312        mWifiConnectivityManager.handleConnectionStateChanged(
313                WifiConnectivityManager.WIFI_STATE_CONNECTED);
314
315        // Set screen to on
316        mWifiConnectivityManager.handleScreenStateChanged(true);
317
318        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
319                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
320    }
321
322    /**
323     * Multiple back to back connection attempts within the rate interval should be rate limited.
324     *
325     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
326     * with the expected candidate network ID and BSSID for only the expected number of times within
327     * the given interval.
328     */
329    @Test
330    public void connectionAttemptRateLimitedWhenScreenOff() {
331        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
332        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
333        int numAttempts = 0;
334        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
335
336        mWifiConnectivityManager.handleScreenStateChanged(false);
337
338        // First attempt the max rate number of connections within the rate interval.
339        long currentTimeStamp = 0;
340        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
341            currentTimeStamp += connectionAttemptIntervals;
342            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
343            // Set WiFi to disconnected state to trigger PNO scan
344            mWifiConnectivityManager.handleConnectionStateChanged(
345                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
346            numAttempts++;
347        }
348        // Now trigger another connection attempt before the rate interval, this should be
349        // skipped because we've crossed rate limit.
350        when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
351        // Set WiFi to disconnected state to trigger PNO scan
352        mWifiConnectivityManager.handleConnectionStateChanged(
353                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
354
355        // Verify that we attempt to connect upto the rate.
356        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
357                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
358    }
359
360    /**
361     * Multiple back to back connection attempts outside the rate interval should not be rate
362     * limited.
363     *
364     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
365     * with the expected candidate network ID and BSSID for only the expected number of times within
366     * the given interval.
367     */
368    @Test
369    public void connectionAttemptNotRateLimitedWhenScreenOff() {
370        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
371        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
372        int numAttempts = 0;
373        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
374
375        mWifiConnectivityManager.handleScreenStateChanged(false);
376
377        // First attempt the max rate number of connections within the rate interval.
378        long currentTimeStamp = 0;
379        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
380            currentTimeStamp += connectionAttemptIntervals;
381            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
382            // Set WiFi to disconnected state to trigger PNO scan
383            mWifiConnectivityManager.handleConnectionStateChanged(
384                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
385            numAttempts++;
386        }
387        // Now trigger another connection attempt after the rate interval, this should not be
388        // skipped because we should've evicted the older attempt.
389        when(mClock.currentTimeMillis()).thenReturn(
390                currentTimeStamp + connectionAttemptIntervals * 2);
391        // Set WiFi to disconnected state to trigger PNO scan
392        mWifiConnectivityManager.handleConnectionStateChanged(
393                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
394        numAttempts++;
395
396        // Verify that all the connection attempts went through
397        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
398                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
399    }
400
401    /**
402     * Multiple back to back connection attempts after a user selection should not be rate limited.
403     *
404     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
405     * with the expected candidate network ID and BSSID for only the expected number of times within
406     * the given interval.
407     */
408    @Test
409    public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() {
410        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
411        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
412        int numAttempts = 0;
413        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
414
415        mWifiConnectivityManager.handleScreenStateChanged(false);
416
417        // First attempt the max rate number of connections within the rate interval.
418        long currentTimeStamp = 0;
419        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
420            currentTimeStamp += connectionAttemptIntervals;
421            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
422            // Set WiFi to disconnected state to trigger PNO scan
423            mWifiConnectivityManager.handleConnectionStateChanged(
424                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
425            numAttempts++;
426        }
427
428        mWifiConnectivityManager.connectToUserSelectNetwork(CANDIDATE_NETWORK_ID, false);
429
430        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
431            currentTimeStamp += connectionAttemptIntervals;
432            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
433            // Set WiFi to disconnected state to trigger PNO scan
434            mWifiConnectivityManager.handleConnectionStateChanged(
435                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
436            numAttempts++;
437        }
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     *  PNO retry for low RSSI networks.
446     *
447     * Expected behavior: WifiConnectivityManager doubles the low RSSI
448     * network retry delay value after QNS skips the PNO scan results
449     * because of their low RSSI values.
450     */
451    @Test
452    public void PnoRetryForLowRssiNetwork() {
453        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
454              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
455
456        // Set screen to off
457        mWifiConnectivityManager.handleScreenStateChanged(false);
458
459        // Get the current retry delay value
460        int lowRssiNetworkRetryDelayStartValue = mWifiConnectivityManager
461                .getLowRssiNetworkRetryDelay();
462
463        // Set WiFi to disconnected state to trigger PNO scan
464        mWifiConnectivityManager.handleConnectionStateChanged(
465                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
466
467        // Get the retry delay value after QNS didn't select a
468        // network candicate from the PNO scan results.
469        int lowRssiNetworkRetryDelayAfterPnoValue = mWifiConnectivityManager
470                .getLowRssiNetworkRetryDelay();
471
472        assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
473            lowRssiNetworkRetryDelayAfterPnoValue);
474    }
475
476    /**
477     * Ensure that the watchdog bite increments the "Pno bad" metric.
478     *
479     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
480     * a candidate while watchdog single scan did.
481     */
482    @Test
483    public void watchdogBitePnoBadIncrementsMetrics() {
484        // Set screen to off
485        mWifiConnectivityManager.handleScreenStateChanged(false);
486
487        // Set WiFi to disconnected state to trigger PNO scan
488        mWifiConnectivityManager.handleConnectionStateChanged(
489                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
490
491        // Now fire the watchdog alarm and verify the metrics were incremented.
492        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
493        mLooper.dispatchAll();
494
495        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoBad();
496        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoGood();
497    }
498
499    /**
500     * Ensure that the watchdog bite increments the "Pno good" metric.
501     *
502     * Expected behavior: WifiConnectivityManager detects that the PNO scan failed to find
503     * a candidate which was the same with watchdog single scan.
504     */
505    @Test
506    public void watchdogBitePnoGoodIncrementsMetrics() {
507        // Qns returns no candidate after watchdog single scan.
508        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
509                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
510
511        // Set screen to off
512        mWifiConnectivityManager.handleScreenStateChanged(false);
513
514        // Set WiFi to disconnected state to trigger PNO scan
515        mWifiConnectivityManager.handleConnectionStateChanged(
516                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
517
518        // Now fire the watchdog alarm and verify the metrics were incremented.
519        mAlarmManager.dispatch(WifiConnectivityManager.WATCHDOG_TIMER_TAG);
520        mLooper.dispatchAll();
521
522        verify(mWifiMetrics).incrementNumConnectivityWatchdogPnoGood();
523        verify(mWifiMetrics, never()).incrementNumConnectivityWatchdogPnoBad();
524    }
525
526    /**
527     *  Verify that scan interval for screen on and wifi disconnected scenario
528     *  is in the exponential backoff fashion.
529     *
530     * Expected behavior: WifiConnectivityManager doubles periodic
531     * scan interval.
532     */
533    @Test
534    public void checkPeriodicScanIntervalWhenDisconnected() {
535        when(mClock.currentTimeMillis()).thenReturn(CURRENT_SYSTEM_TIME_MS);
536
537        // Set screen to ON
538        mWifiConnectivityManager.handleScreenStateChanged(true);
539
540        // Set WiFi to disconnected state to trigger periodic scan
541        mWifiConnectivityManager.handleConnectionStateChanged(
542                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
543
544        // Get the first periodic scan interval
545        long firstIntervalMs = mAlarmManager
546                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
547                    - CURRENT_SYSTEM_TIME_MS;
548        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
549
550        // Now fire the first periodic scan alarm timer
551        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
552        mLooper.dispatchAll();
553
554        // Get the second periodic scan interval
555        long secondIntervalMs = mAlarmManager
556                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
557                    - CURRENT_SYSTEM_TIME_MS;
558
559        // Verify the intervals are exponential back off
560        assertEquals(firstIntervalMs * 2, secondIntervalMs);
561
562        // Make sure we eventually stay at the maximum scan interval.
563        long intervalMs = 0;
564        for (int i = 0; i < 5; i++) {
565            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
566            mLooper.dispatchAll();
567            intervalMs = mAlarmManager
568                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
569                    - CURRENT_SYSTEM_TIME_MS;
570        }
571
572        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
573    }
574
575    /**
576     *  Verify that scan interval for screen on and wifi connected scenario
577     *  is in the exponential backoff fashion.
578     *
579     * Expected behavior: WifiConnectivityManager doubles periodic
580     * scan interval.
581     */
582    @Test
583    public void checkPeriodicScanIntervalWhenConnected() {
584        when(mClock.currentTimeMillis()).thenReturn(CURRENT_SYSTEM_TIME_MS);
585
586        // Set screen to ON
587        mWifiConnectivityManager.handleScreenStateChanged(true);
588
589        // Set WiFi to connected state to trigger periodic scan
590        mWifiConnectivityManager.handleConnectionStateChanged(
591                WifiConnectivityManager.WIFI_STATE_CONNECTED);
592
593        // Get the first periodic scan interval
594        long firstIntervalMs = mAlarmManager
595                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
596                    - CURRENT_SYSTEM_TIME_MS;
597        assertEquals(firstIntervalMs, WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
598
599        // Now fire the first periodic scan alarm timer
600        mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
601        mLooper.dispatchAll();
602
603        // Get the second periodic scan interval
604        long secondIntervalMs = mAlarmManager
605                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
606                    - CURRENT_SYSTEM_TIME_MS;
607
608        // Verify the intervals are exponential back off
609        assertEquals(firstIntervalMs * 2, secondIntervalMs);
610
611        // Make sure we eventually stay at the maximum scan interval.
612        long intervalMs = 0;
613        for (int i = 0; i < 5; i++) {
614            mAlarmManager.dispatch(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
615            mLooper.dispatchAll();
616            intervalMs = mAlarmManager
617                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
618                    - CURRENT_SYSTEM_TIME_MS;
619        }
620
621        assertEquals(intervalMs, WifiConnectivityManager.MAX_PERIODIC_SCAN_INTERVAL_MS);
622    }
623
624    /**
625     * Verify that we perform full band scan when the currently connected network's tx/rx success
626     * rate is low.
627     *
628     * Expected behavior: WifiConnectivityManager does full band scan.
629     */
630    @Test
631    public void checkSingleScanSettingsWhenConnectedWithLowDataRate() {
632        mWifiInfo.txSuccessRate = 0;
633        mWifiInfo.rxSuccessRate = 0;
634
635        final HashSet<Integer> channelList = new HashSet<>();
636        channelList.add(1);
637        channelList.add(2);
638        channelList.add(3);
639
640        when(mWifiStateMachine.getCurrentWifiConfiguration())
641                .thenReturn(new WifiConfiguration());
642        when(mWifiStateMachine.getFrequencyBand())
643                .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ);
644        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
645                .thenReturn(channelList);
646
647        doAnswer(new AnswerWithArguments() {
648            public void answer(ScanSettings settings, ScanListener listener,
649                    WorkSource workSource) throws Exception {
650                assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS);
651                assertNull(settings.channels);
652            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
653
654        // Set screen to ON
655        mWifiConnectivityManager.handleScreenStateChanged(true);
656
657        // Set WiFi to connected state to trigger periodic scan
658        mWifiConnectivityManager.handleConnectionStateChanged(
659                WifiConnectivityManager.WIFI_STATE_CONNECTED);
660
661        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
662    }
663
664    /**
665     * Verify that we perform partial scan when the currently connected network's tx/rx success
666     * rate is high and when the currently connected network is present in scan
667     * cache in WifiConfigManager.
668     *
669     * Expected behavior: WifiConnectivityManager does full band scan.
670     */
671    @Test
672    public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
673        mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
674        mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
675
676        final HashSet<Integer> channelList = new HashSet<>();
677        channelList.add(1);
678        channelList.add(2);
679        channelList.add(3);
680
681        when(mWifiStateMachine.getCurrentWifiConfiguration())
682                .thenReturn(new WifiConfiguration());
683        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
684                .thenReturn(channelList);
685
686        doAnswer(new AnswerWithArguments() {
687            public void answer(ScanSettings settings, ScanListener listener,
688                    WorkSource workSource) throws Exception {
689                assertEquals(settings.band, WifiScanner.WIFI_BAND_UNSPECIFIED);
690                assertEquals(settings.channels.length, channelList.size());
691                for (int chanIdx = 0; chanIdx < settings.channels.length; chanIdx++) {
692                    assertTrue(channelList.contains(settings.channels[chanIdx].frequency));
693                }
694            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
695
696        // Set screen to ON
697        mWifiConnectivityManager.handleScreenStateChanged(true);
698
699        // Set WiFi to connected state to trigger periodic scan
700        mWifiConnectivityManager.handleConnectionStateChanged(
701                WifiConnectivityManager.WIFI_STATE_CONNECTED);
702
703        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
704    }
705
706    /**
707     * Verify that we fall back to full band scan when the currently connected network's tx/rx
708     * success rate is high and the currently connected network is not present in scan cache in
709     * WifiConfigManager. This is simulated by returning an empty hashset in |makeChannelList|.
710     *
711     * Expected behavior: WifiConnectivityManager does full band scan.
712     */
713    @Test
714    public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
715        mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
716        mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
717
718        final HashSet<Integer> channelList = new HashSet<>();
719
720        when(mWifiStateMachine.getCurrentWifiConfiguration())
721                .thenReturn(new WifiConfiguration());
722        when(mWifiStateMachine.getFrequencyBand())
723                .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ);
724        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
725                .thenReturn(channelList);
726
727        doAnswer(new AnswerWithArguments() {
728            public void answer(ScanSettings settings, ScanListener listener,
729                    WorkSource workSource) throws Exception {
730                assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS);
731                assertNull(settings.channels);
732            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
733
734        // Set screen to ON
735        mWifiConnectivityManager.handleScreenStateChanged(true);
736
737        // Set WiFi to connected state to trigger periodic scan
738        mWifiConnectivityManager.handleConnectionStateChanged(
739                WifiConnectivityManager.WIFI_STATE_CONNECTED);
740
741        verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
742    }
743}
744