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