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.scanner;
18
19import static com.android.server.wifi.ScanTestUtil.NativeScanSettingsBuilder;
20import static com.android.server.wifi.ScanTestUtil.assertScanDataEquals;
21
22import static org.junit.Assert.*;
23import static org.mockito.Mockito.*;
24
25import android.content.Context;
26import android.net.wifi.WifiConfiguration;
27import android.net.wifi.WifiScanner;
28import android.os.SystemClock;
29import android.test.suitebuilder.annotation.SmallTest;
30
31import com.android.internal.R;
32import com.android.server.wifi.Clock;
33import com.android.server.wifi.MockAlarmManager;
34import com.android.server.wifi.MockLooper;
35import com.android.server.wifi.MockResources;
36import com.android.server.wifi.MockWifiMonitor;
37import com.android.server.wifi.ScanResults;
38import com.android.server.wifi.WifiMonitor;
39import com.android.server.wifi.WifiNative;
40import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
41
42import org.junit.Before;
43import org.junit.Test;
44import org.mockito.InOrder;
45import org.mockito.Mock;
46import org.mockito.MockitoAnnotations;
47
48import java.util.Arrays;
49import java.util.HashSet;
50import java.util.Set;
51
52
53/**
54 * Unit tests for {@link com.android.server.wifi.scanner.SupplicantWifiScannerImpl.setPnoList}.
55 */
56@SmallTest
57public class SupplicantPnoScannerTest {
58
59    @Mock Context mContext;
60    MockAlarmManager mAlarmManager;
61    MockWifiMonitor mWifiMonitor;
62    MockLooper mLooper;
63    @Mock WifiNative mWifiNative;
64    MockResources mResources;
65    @Mock Clock mClock;
66    SupplicantWifiScannerImpl mScanner;
67
68    @Before
69    public void setup() throws Exception {
70        MockitoAnnotations.initMocks(this);
71
72        mLooper = new MockLooper();
73        mAlarmManager = new MockAlarmManager();
74        mWifiMonitor = new MockWifiMonitor();
75        mResources = new MockResources();
76
77        when(mWifiNative.getInterfaceName()).thenReturn("a_test_interface_name");
78        when(mContext.getSystemService(Context.ALARM_SERVICE))
79                .thenReturn(mAlarmManager.getAlarmManager());
80        when(mContext.getResources()).thenReturn(mResources);
81        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
82    }
83
84    /**
85     * Verify that the HW disconnected PNO scan triggers a supplicant PNO scan and invokes the
86     * OnPnoNetworkFound callback when the scan results are received.
87     */
88    @Test
89    public void startHwDisconnectedPnoScan() {
90        createScannerWithHwPnoScanSupport();
91
92        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
93        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
94        ScanResults scanResults = createDummyScanResults(false);
95
96        InOrder order = inOrder(pnoEventHandler, mWifiNative);
97        // Start PNO scan
98        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
99        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
100        verifyNoMoreInteractions(pnoEventHandler);
101    }
102
103    /**
104     * Verify that we pause & resume HW PNO scan when a single scan is scheduled and invokes the
105     * OnPnoNetworkFound callback when the scan results are received.
106     */
107    @Test
108    public void pauseResumeHwDisconnectedPnoScanForSingleScan() {
109        createScannerWithHwPnoScanSupport();
110
111        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
112        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
113        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
114        WifiNative.ScanSettings settings = createDummyScanSettings();
115        ScanResults scanResults = createDummyScanResults(true);
116
117        InOrder order = inOrder(eventHandler, mWifiNative);
118        // Start PNO scan
119        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
120        // Start single scan
121        assertTrue(mScanner.startSingleScan(settings, eventHandler));
122        // Verify that the PNO scan was paused and single scan runs successfully
123        expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
124                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>(),
125                scanResults);
126        verifyNoMoreInteractions(eventHandler);
127
128        order = inOrder(pnoEventHandler, mWifiNative);
129        // Resume PNO scan after the single scan results are received and PNO monitor debounce
130        // alarm fires.
131        assertTrue("dispatch pno monitor alarm",
132                mAlarmManager.dispatch(
133                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
134        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
135        // Now verify that PNO scan is resumed successfully
136        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
137        verifyNoMoreInteractions(pnoEventHandler);
138    }
139
140    /**
141     * Verify that the SW disconnected PNO scan triggers a background scan and invokes the
142     * background scan callbacks when scan results are received.
143     */
144    @Test
145    public void startSwDisconnectedPnoScan() {
146        createScannerWithSwPnoScanSupport();
147        doSuccessfulSwPnoScanTest(false);
148    }
149
150    /**
151     * Verify that the HW connected PNO scan triggers a background scan and invokes the
152     * background scan callbacks when scan results are received.
153     */
154    @Test
155    public void startHwConnectedPnoScan() {
156        createScannerWithHwPnoScanSupport();
157        doSuccessfulSwPnoScanTest(true);
158    }
159
160    /**
161     * Verify that the SW connected PNO scan triggers a background scan and invokes the
162     * background scan callbacks when scan results are received.
163     */
164    @Test
165    public void startSwConnectedPnoScan() {
166        createScannerWithSwPnoScanSupport();
167        doSuccessfulSwPnoScanTest(true);
168    }
169
170    /**
171     * Verify that the HW PNO delayed failure cleans up the scan settings cleanly.
172     * 1. Start Hw PNO.
173     * 2. Start Single Scan which should pause PNO scan.
174     * 3. Fail the PNO scan resume and verify that the OnPnoScanFailed callback is invoked.
175     * 4. Now restart a new PNO scan to ensure that the failure was cleanly handled.
176     */
177    @Test
178    public void delayedHwDisconnectedPnoScanFailure() {
179        createScannerWithHwPnoScanSupport();
180
181        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
182        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
183        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
184        WifiNative.ScanSettings settings = createDummyScanSettings();
185        ScanResults scanResults = createDummyScanResults(true);
186
187        InOrder order = inOrder(eventHandler, mWifiNative);
188        // Start PNO scan
189        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
190        // Start single scan
191        assertTrue(mScanner.startSingleScan(settings, eventHandler));
192        // Verify that the PNO scan was paused and single scan runs successfully
193        expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
194                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>(),
195                scanResults);
196        verifyNoMoreInteractions(eventHandler);
197
198        // Fail the PNO resume and check that the OnPnoScanFailed callback is invoked.
199        order = inOrder(pnoEventHandler, mWifiNative);
200        when(mWifiNative.setPnoScan(true)).thenReturn(false);
201        assertTrue("dispatch pno monitor alarm",
202                mAlarmManager.dispatch(
203                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
204        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
205        order.verify(pnoEventHandler).onPnoScanFailed();
206        verifyNoMoreInteractions(pnoEventHandler);
207
208        // Add a new PNO scan request
209        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
210        assertTrue("dispatch pno monitor alarm",
211                mAlarmManager.dispatch(
212                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
213        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
214        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
215        verifyNoMoreInteractions(pnoEventHandler);
216    }
217
218    /**
219     * Verify that the HW PNO scan stop failure still resets the PNO scan state.
220     * 1. Start Hw PNO.
221     * 2. Stop Hw PNO scan which raises a stop command to WifiNative which is failed.
222     * 3. Now restart a new PNO scan to ensure that the failure was cleanly handled.
223     */
224    @Test
225    public void ignoreHwDisconnectedPnoScanStopFailure() {
226        createScannerWithHwPnoScanSupport();
227
228        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
229        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
230
231        // Start PNO scan
232        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
233
234        // Fail the PNO stop.
235        when(mWifiNative.setPnoScan(false)).thenReturn(false);
236        assertTrue(mScanner.resetHwPnoList());
237        assertTrue("dispatch pno monitor alarm",
238                mAlarmManager.dispatch(
239                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
240        mLooper.dispatchAll();
241        verify(mWifiNative).setPnoScan(false);
242
243        // Add a new PNO scan request and ensure it runs successfully.
244        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
245        assertTrue("dispatch pno monitor alarm",
246                mAlarmManager.dispatch(
247                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
248        mLooper.dispatchAll();
249        InOrder order = inOrder(pnoEventHandler, mWifiNative);
250        ScanResults scanResults = createDummyScanResults(false);
251        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
252        verifyNoMoreInteractions(pnoEventHandler);
253    }
254
255    /**
256     * Verify that the HW PNO scan is forcefully stopped (bypass debounce logic) and restarted when
257     * settings change.
258     * 1. Start Hw PNO.
259     * 2. Stop Hw PNO .
260     * 3. Now restart a new PNO scan with different settings.
261     * 4. Ensure that the stop was issued before we start again.
262     */
263    @Test
264    public void forceRestartHwDisconnectedPnoScanWhenSettingsChange() {
265        createScannerWithHwPnoScanSupport();
266
267        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
268        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
269        InOrder order = inOrder(pnoEventHandler, mWifiNative);
270
271        // Start PNO scan
272        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
273        expectHwDisconnectedPnoScanStart(order, pnoSettings);
274
275        // Stop PNO now. This should trigger the debounce timer and not stop PNO.
276        assertTrue(mScanner.resetHwPnoList());
277        assertTrue(mAlarmManager.isPending(
278                SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
279        order.verify(mWifiNative, never()).setPnoScan(false);
280
281        // Now restart PNO scan with an extra network in settings.
282        pnoSettings.networkList =
283                Arrays.copyOf(pnoSettings.networkList, pnoSettings.networkList.length + 1);
284        pnoSettings.networkList[pnoSettings.networkList.length - 1] =
285                createDummyPnoNetwork("ssid_pno_new", 6, 6);
286        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
287
288        // This should bypass the debounce timer and stop PNO scan immediately and then start
289        // a new debounce timer for the start.
290        order.verify(mWifiNative).setPnoScan(false);
291
292        // Trigger the debounce timer and ensure we start PNO scan again.
293        mAlarmManager.dispatch(SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG);
294        mLooper.dispatchAll();
295        order.verify(mWifiNative).setPnoScan(true);
296    }
297
298    /**
299     * Verify that the HW PNO scan is not forcefully stopped (bypass debounce logic) when
300     * settings don't change.
301     * 1. Start Hw PNO.
302     * 2. Stop Hw PNO .
303     * 3. Now restart a new PNO scan with same settings.
304     * 4. Ensure that the stop was never issued.
305     */
306    @Test
307    public void noForceRestartHwDisconnectedPnoScanWhenNoSettingsChange() {
308        createScannerWithHwPnoScanSupport();
309
310        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
311        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
312        InOrder order = inOrder(pnoEventHandler, mWifiNative);
313
314        // Start PNO scan
315        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
316        expectHwDisconnectedPnoScanStart(order, pnoSettings);
317
318        // Stop PNO now. This should trigger the debounce timer and not stop PNO.
319        assertTrue(mScanner.resetHwPnoList());
320        assertTrue(mAlarmManager.isPending(
321                SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
322        order.verify(mWifiNative, never()).setPnoScan(false);
323
324        // Now restart PNO scan with the same settings.
325        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
326
327        // Trigger the debounce timer and ensure that we neither stop/start.
328        mLooper.dispatchAll();
329        order.verify(mWifiNative, never()).setPnoScan(anyBoolean());
330    }
331
332    private void doSuccessfulSwPnoScanTest(boolean isConnectedPno) {
333        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
334        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(isConnectedPno);
335        WifiNative.ScanEventHandler scanEventHandler = mock(WifiNative.ScanEventHandler.class);
336        WifiNative.ScanSettings scanSettings = createDummyScanSettings();
337        ScanResults scanResults = createDummyScanResults(false);
338
339        InOrder order = inOrder(scanEventHandler, mWifiNative);
340
341        // Start PNO scan
342        startSuccessfulPnoScan(scanSettings, pnoSettings, scanEventHandler, pnoEventHandler);
343
344        expectSuccessfulSwPnoScan(order, scanEventHandler, scanResults);
345
346        verifyNoMoreInteractions(pnoEventHandler);
347    }
348
349    private void createScannerWithHwPnoScanSupport() {
350        mResources.setBoolean(R.bool.config_wifi_background_scan_support, true);
351        mScanner =
352                new SupplicantWifiScannerImpl(mContext, mWifiNative, mLooper.getLooper(), mClock);
353    }
354
355    private void createScannerWithSwPnoScanSupport() {
356        mResources.setBoolean(R.bool.config_wifi_background_scan_support, false);
357        mScanner =
358                new SupplicantWifiScannerImpl(mContext, mWifiNative, mLooper.getLooper(), mClock);
359    }
360
361    private WifiNative.PnoNetwork createDummyPnoNetwork(String ssid, int networkId, int priority) {
362        WifiNative.PnoNetwork pnoNetwork = new WifiNative.PnoNetwork();
363        pnoNetwork.ssid = ssid;
364        pnoNetwork.networkId = networkId;
365        pnoNetwork.priority = priority;
366        return pnoNetwork;
367    }
368
369    private WifiNative.PnoSettings createDummyPnoSettings(boolean isConnected) {
370        WifiNative.PnoSettings pnoSettings = new WifiNative.PnoSettings();
371        pnoSettings.isConnected = isConnected;
372        pnoSettings.networkList = new WifiNative.PnoNetwork[2];
373        pnoSettings.networkList[0] = createDummyPnoNetwork("ssid_pno_1", 1, 1);
374        pnoSettings.networkList[1] = createDummyPnoNetwork("ssid_pno_2", 2, 2);
375        return pnoSettings;
376    }
377
378    private WifiNative.ScanSettings createDummyScanSettings() {
379        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
380                .withBasePeriod(10000)
381                .withMaxApPerScan(10)
382                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
383                        WifiScanner.WIFI_BAND_24_GHZ)
384                .build();
385        return settings;
386    }
387
388    private ScanResults createDummyScanResults(boolean allChannelsScanned) {
389        return ScanResults.create(0, allChannelsScanned, 2400, 2450, 2450, 2400, 2450, 2450, 2400,
390                2450, 2450);
391    }
392
393    private void startSuccessfulPnoScan(WifiNative.ScanSettings scanSettings,
394            WifiNative.PnoSettings pnoSettings, WifiNative.ScanEventHandler scanEventHandler,
395            WifiNative.PnoEventHandler pnoEventHandler) {
396        reset(mWifiNative);
397        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenReturn(true);
398        when(mWifiNative.enableNetworkWithoutConnect(anyInt())).thenReturn(true);
399        // Scans succeed
400        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
401        when(mWifiNative.setPnoScan(anyBoolean())).thenReturn(true);
402
403        if (mScanner.isHwPnoSupported(pnoSettings.isConnected)) {
404            // This should happen only for HW PNO scan
405            assertTrue(mScanner.setHwPnoList(pnoSettings, pnoEventHandler));
406        } else {
407            // This should happen only for SW PNO scan
408            assertTrue(mScanner.startBatchedScan(scanSettings, scanEventHandler));
409
410        }
411    }
412
413    private Set<Integer> expectedBandScanFreqs(int band) {
414        ChannelCollection collection = mScanner.getChannelHelper().createChannelCollection();
415        collection.addBand(band);
416        return collection.getSupplicantScanFreqs();
417    }
418
419    /**
420     * Verify that the PNO scan was successfully started.
421     */
422    private void expectHwDisconnectedPnoScanStart(InOrder order,
423            WifiNative.PnoSettings pnoSettings) {
424        for (int i = 0; i < pnoSettings.networkList.length; i++) {
425            WifiNative.PnoNetwork network = pnoSettings.networkList[i];
426            order.verify(mWifiNative).setNetworkVariable(network.networkId,
427                    WifiConfiguration.priorityVarName, Integer.toString(network.priority));
428            order.verify(mWifiNative).enableNetworkWithoutConnect(network.networkId);
429        }
430        // Verify  HW PNO scan started
431        order.verify(mWifiNative).setPnoScan(true);
432    }
433
434    /**
435     *
436     * 1. Verify that the PNO scan was successfully started.
437     * 2. Send scan results and ensure that the |onPnoNetworkFound| callback was called.
438     */
439    private void expectSuccessfulHwDisconnectedPnoScan(InOrder order,
440            WifiNative.PnoSettings pnoSettings, WifiNative.PnoEventHandler eventHandler,
441            ScanResults scanResults) {
442        expectHwDisconnectedPnoScanStart(order, pnoSettings);
443
444        // Setup scan results
445        when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
446
447        // Notify scan has finished
448        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
449        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
450
451        order.verify(eventHandler).onPnoNetworkFound(scanResults.getRawScanResults());
452    }
453
454    /**
455     * Verify that the single scan results were delivered and that the PNO scan was paused and
456     * resumed either side of it.
457     */
458    private void expectSuccessfulSingleScanWithHwPnoEnabled(InOrder order,
459            WifiNative.ScanEventHandler eventHandler, Set<Integer> expectedScanFreqs,
460            Set<Integer> expectedHiddenNetIds, ScanResults scanResults) {
461        // Pause PNO scan first
462        order.verify(mWifiNative).setPnoScan(false);
463
464        order.verify(mWifiNative).scan(eq(expectedScanFreqs), eq(expectedHiddenNetIds));
465
466        when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
467
468        // Notify scan has finished
469        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
470        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
471
472        order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
473        assertScanDataEquals(scanResults.getScanData(), mScanner.getLatestSingleScanResults());
474    }
475
476    /**
477     * Verify that the SW PNO scan was successfully started. This could either be disconnected
478     * or connected PNO.
479     * This is basically ensuring that the background scan runs successfully and returns the
480     * expected result.
481     */
482    private void expectSuccessfulSwPnoScan(InOrder order,
483            WifiNative.ScanEventHandler eventHandler, ScanResults scanResults) {
484
485        // Verify scan started
486        order.verify(mWifiNative).scan(any(Set.class), any(Set.class));
487
488        // Make sure that HW PNO scan was not started
489        verify(mWifiNative, never()).setPnoScan(anyBoolean());
490
491        // Setup scan results
492        when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
493
494        // Notify scan has finished
495        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
496        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
497
498        // Verify background scan results delivered
499        order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
500        WifiScanner.ScanData[] scanData = mScanner.getLatestBatchedScanResults(true);
501        WifiScanner.ScanData lastScanData = scanData[scanData.length -1];
502        assertScanDataEquals(scanResults.getScanData(), lastScanData);
503    }
504}
505