WifiConnectivityManagerTest.java revision 50abba06efa7834b5309df561375e4a2e2df630d
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.mockito.Mockito.*;
22
23import android.app.AlarmManager;
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.WifiScanner;
32import android.net.wifi.WifiSsid;
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.os.Looper;
38import android.os.WorkSource;
39import android.test.suitebuilder.annotation.SmallTest;
40
41import com.android.internal.R;
42import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
43import com.android.server.wifi.util.InformationElementUtil;
44
45import org.junit.After;
46import org.junit.Before;
47import org.junit.Test;
48
49import java.nio.charset.StandardCharsets;
50import java.util.ArrayList;
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 = mockAlarmManager();
67        mContext = mockContext();
68        mWifiStateMachine = mockWifiStateMachine();
69        mWifiConfigManager = mockWifiConfigManager();
70        mWifiInfo = mockWifiInfo();
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 AlarmManager 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 WifiInjector mWifiInjector;
101
102    private static final int CANDIDATE_NETWORK_ID = 0;
103    private static final String CANDIDATE_SSID = "\"AnSsid\"";
104    private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
105    private static final String TAG = "WifiConnectivityManager Unit Test";
106
107    Resources mockResource() {
108        Resources resource = mock(Resources.class);
109
110        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
111        when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
112
113        return resource;
114    }
115
116    AlarmManager mockAlarmManager() {
117        AlarmManager alarmManager = mock(AlarmManager.class);
118
119        return alarmManager;
120    }
121
122    Context mockContext() {
123        Context context = mock(Context.class);
124
125        when(context.getResources()).thenReturn(mResource);
126        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
127                mAlarmManager);
128
129        return context;
130    }
131
132    WifiScanner mockWifiScanner() {
133        WifiScanner scanner = mock(WifiScanner.class);
134
135        // dummy scan results. QNS PeriodicScanListener bulids scanDetails from
136        // the fullScanResult and doesn't really use results
137        final WifiScanner.ScanData[] scanDatas = new WifiScanner.ScanData[1];
138
139        // do a synchronous answer for the ScanListener callbacks
140        doAnswer(new AnswerWithArguments() {
141                public void answer(ScanSettings settings, ScanListener listener,
142                        WorkSource workSource) throws Exception {
143                    listener.onResults(scanDatas);
144                }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
145
146        doAnswer(new AnswerWithArguments() {
147                public void answer(ScanSettings settings, ScanListener listener,
148                        WorkSource workSource) throws Exception {
149                    listener.onResults(scanDatas);
150                }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
151
152        // This unfortunately needs to be a somewhat valid scan result, otherwise
153        // |ScanDetailUtil.toScanDetail| raises exceptions.
154        final ScanResult[] scanResults = new ScanResult[1];
155        scanResults[0] = new ScanResult(WifiSsid.createFromAsciiEncoded(CANDIDATE_SSID),
156                CANDIDATE_SSID, CANDIDATE_BSSID, 1245, 0, "some caps",
157                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
158        scanResults[0].informationElements = new InformationElement[1];
159        scanResults[0].informationElements[0] = new InformationElement();
160        scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
161        scanResults[0].informationElements[0].bytes =
162                CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
163
164        doAnswer(new AnswerWithArguments() {
165            public void answer(ScanSettings settings, PnoSettings pnoSettings,
166                    PnoScanListener listener) throws Exception {
167                listener.onPnoNetworkFound(scanResults);
168            }}).when(scanner).startDisconnectedPnoScan(anyObject(), anyObject(), anyObject());
169
170        doAnswer(new AnswerWithArguments() {
171            public void answer(ScanSettings settings, PnoSettings pnoSettings,
172                    PnoScanListener listener) throws Exception {
173                listener.onPnoNetworkFound(scanResults);
174            }}).when(scanner).startConnectedPnoScan(anyObject(), anyObject(), anyObject());
175
176        return scanner;
177    }
178
179    WifiStateMachine mockWifiStateMachine() {
180        WifiStateMachine stateMachine = mock(WifiStateMachine.class);
181
182        when(stateMachine.getFrequencyBand()).thenReturn(1);
183        when(stateMachine.isLinkDebouncing()).thenReturn(false);
184        when(stateMachine.isConnected()).thenReturn(false);
185        when(stateMachine.isDisconnected()).thenReturn(true);
186        when(stateMachine.isSupplicantTransientState()).thenReturn(false);
187
188        return stateMachine;
189    }
190
191    WifiQualifiedNetworkSelector mockWifiQualifiedNetworkSelector() {
192        WifiQualifiedNetworkSelector qns = mock(WifiQualifiedNetworkSelector.class);
193
194        WifiConfiguration candidate = generateWifiConfig(
195                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
196        candidate.BSSID = CANDIDATE_BSSID;
197        ScanResult candidateScanResult = new ScanResult();
198        candidateScanResult.SSID = CANDIDATE_SSID;
199        candidateScanResult.BSSID = CANDIDATE_BSSID;
200        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
201
202        when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
203              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(candidate);
204        return qns;
205    }
206
207    WifiInfo mockWifiInfo() {
208        WifiInfo wifiInfo = mock(WifiInfo.class);
209
210        when(wifiInfo.getNetworkId()).thenReturn(WifiConfiguration.INVALID_NETWORK_ID);
211        when(wifiInfo.getBSSID()).thenReturn(null);
212        when(wifiInfo.getSupplicantState()).thenReturn(SupplicantState.DISCONNECTED);
213
214        return wifiInfo;
215    }
216
217    WifiConfigManager mockWifiConfigManager() {
218        WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
219
220        when(wifiConfigManager.getWifiConfiguration(anyInt())).thenReturn(null);
221        wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger(
222                WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND);
223        wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger(
224                WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD);
225
226        // Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
227        PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
228        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
229        pnoNetworkList.add(pnoNetwork);
230        when(wifiConfigManager.retrieveDisconnectedPnoNetworkList()).thenReturn(pnoNetworkList);
231        when(wifiConfigManager.retrieveConnectedPnoNetworkList()).thenReturn(pnoNetworkList);
232
233        return wifiConfigManager;
234    }
235
236    WifiInjector mockWifiInjector() {
237        WifiInjector wifiInjector = mock(WifiInjector.class);
238        mWifiLastResortWatchdog = mock(WifiLastResortWatchdog.class);
239        when(wifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
240        when(wifiInjector.getClock()).thenReturn(mClock);
241        return wifiInjector;
242    }
243
244    /**
245     *  Wifi enters disconnected state while screen is on.
246     *
247     * Expected behavior: WifiConnectivityManager calls
248     * WifiStateMachine.autoConnectToNetwork() with the
249     * expected candidate network ID and BSSID.
250     */
251    @Test
252    public void enterWifiDisconnectedStateWhenScreenOn() {
253        // Set screen to on
254        mWifiConnectivityManager.handleScreenStateChanged(true);
255
256        // Set WiFi to disconnected state
257        mWifiConnectivityManager.handleConnectionStateChanged(
258                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
259
260        verify(mWifiStateMachine).autoConnectToNetwork(
261                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
262    }
263
264    /**
265     *  Wifi enters connected state while screen is on.
266     *
267     * Expected behavior: WifiConnectivityManager calls
268     * WifiStateMachine.autoConnectToNetwork() with the
269     * expected candidate network ID and BSSID.
270     */
271    @Test
272    public void enterWifiConnectedStateWhenScreenOn() {
273        // Set screen to on
274        mWifiConnectivityManager.handleScreenStateChanged(true);
275
276        // Set WiFi to connected state
277        mWifiConnectivityManager.handleConnectionStateChanged(
278                WifiConnectivityManager.WIFI_STATE_CONNECTED);
279
280        verify(mWifiStateMachine).autoConnectToNetwork(
281                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
282    }
283
284    /**
285     *  Screen turned on while WiFi in disconnected state.
286     *
287     * Expected behavior: WifiConnectivityManager calls
288     * WifiStateMachine.autoConnectToNetwork() with the
289     * expected candidate network ID and BSSID.
290     */
291    @Test
292    public void turnScreenOnWhenWifiInDisconnectedState() {
293        // Set WiFi to disconnected state
294        mWifiConnectivityManager.handleConnectionStateChanged(
295                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
296
297        // Set screen to on
298        mWifiConnectivityManager.handleScreenStateChanged(true);
299
300        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
301                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
302    }
303
304    /**
305     *  Screen turned on while WiFi in connected state.
306     *
307     * Expected behavior: WifiConnectivityManager calls
308     * WifiStateMachine.autoConnectToNetwork() with the
309     * expected candidate network ID and BSSID.
310     */
311    @Test
312    public void turnScreenOnWhenWifiInConnectedState() {
313        // Set WiFi to connected state
314        mWifiConnectivityManager.handleConnectionStateChanged(
315                WifiConnectivityManager.WIFI_STATE_CONNECTED);
316
317        // Set screen to on
318        mWifiConnectivityManager.handleScreenStateChanged(true);
319
320        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
321                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
322    }
323
324    /**
325     * Multiple back to back connection attempts within the rate interval should be rate limited.
326     *
327     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
328     * with the expected candidate network ID and BSSID for only the expected number of times within
329     * the given interval.
330     */
331    @Test
332    public void connectionAttemptRateLimitedWhenScreenOff() {
333        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
334        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
335        int numAttempts = 0;
336        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
337
338        mWifiConnectivityManager.handleScreenStateChanged(false);
339
340        // First attempt the max rate number of connections within the rate interval.
341        long currentTimeStamp = 0;
342        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
343            currentTimeStamp += connectionAttemptIntervals;
344            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
345            // Set WiFi to disconnected state to trigger PNO scan
346            mWifiConnectivityManager.handleConnectionStateChanged(
347                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
348            numAttempts++;
349        }
350        // Now trigger another connection attempt before the rate interval, this should be
351        // skipped because we've crossed rate limit.
352        when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
353        // Set WiFi to disconnected state to trigger PNO scan
354        mWifiConnectivityManager.handleConnectionStateChanged(
355                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
356
357        // Verify that we attempt to connect upto the rate.
358        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
359                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
360    }
361
362    /**
363     * Multiple back to back connection attempts outside the rate interval should not be rate
364     * limited.
365     *
366     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
367     * with the expected candidate network ID and BSSID for only the expected number of times within
368     * the given interval.
369     */
370    @Test
371    public void connectionAttemptNotRateLimitedWhenScreenOff() {
372        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
373        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
374        int numAttempts = 0;
375        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
376
377        mWifiConnectivityManager.handleScreenStateChanged(false);
378
379        // First attempt the max rate number of connections within the rate interval.
380        long currentTimeStamp = 0;
381        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
382            currentTimeStamp += connectionAttemptIntervals;
383            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
384            // Set WiFi to disconnected state to trigger PNO scan
385            mWifiConnectivityManager.handleConnectionStateChanged(
386                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
387            numAttempts++;
388        }
389        // Now trigger another connection attempt after the rate interval, this should not be
390        // skipped because we should've evicted the older attempt.
391        when(mClock.currentTimeMillis()).thenReturn(
392                currentTimeStamp + connectionAttemptIntervals * 2);
393        // Set WiFi to disconnected state to trigger PNO scan
394        mWifiConnectivityManager.handleConnectionStateChanged(
395                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
396        numAttempts++;
397
398        // Verify that all the connection attempts went through
399        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
400                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
401    }
402
403    /**
404     * Multiple back to back connection attempts after a user selection should not be rate limited.
405     *
406     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
407     * with the expected candidate network ID and BSSID for only the expected number of times within
408     * the given interval.
409     */
410    @Test
411    public void connectionAttemptNotRateLimitedWhenScreenOffAfterUserSelection() {
412        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
413        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
414        int numAttempts = 0;
415        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
416
417        mWifiConnectivityManager.handleScreenStateChanged(false);
418
419        // First attempt the max rate number of connections within the rate interval.
420        long currentTimeStamp = 0;
421        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
422            currentTimeStamp += connectionAttemptIntervals;
423            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
424            // Set WiFi to disconnected state to trigger PNO scan
425            mWifiConnectivityManager.handleConnectionStateChanged(
426                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
427            numAttempts++;
428        }
429
430        mWifiConnectivityManager.connectToUserSelectNetwork(CANDIDATE_NETWORK_ID, false);
431
432        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
433            currentTimeStamp += connectionAttemptIntervals;
434            when(mClock.currentTimeMillis()).thenReturn(currentTimeStamp);
435            // Set WiFi to disconnected state to trigger PNO scan
436            mWifiConnectivityManager.handleConnectionStateChanged(
437                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
438            numAttempts++;
439        }
440
441        // Verify that all the connection attempts went through
442        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
443                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
444    }
445}
446