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 org.junit.Assert.assertEquals;
20import static org.mockito.Mockito.*;
21
22import android.content.Context;
23import android.net.wifi.WifiConfiguration;
24import android.net.wifi.WifiManager;
25import android.os.BatteryStats;
26import android.os.test.TestLooper;
27import android.support.test.filters.SmallTest;
28import android.util.Log;
29
30import com.android.internal.app.IBatteryStats;
31
32import org.junit.After;
33import org.junit.Before;
34import org.junit.Test;
35import org.mockito.ArgumentCaptor;
36import org.mockito.Mock;
37import org.mockito.MockitoAnnotations;
38import org.mockito.invocation.InvocationOnMock;
39import org.mockito.stubbing.Answer;
40
41import java.io.ByteArrayOutputStream;
42import java.io.PrintWriter;
43
44/**
45 * Unit tests for {@link com.android.server.wifi.WifiStateMachinePrime}.
46 */
47@SmallTest
48public class WifiStateMachinePrimeTest {
49    public static final String TAG = "WifiStateMachinePrimeTest";
50
51    private static final String CLIENT_MODE_STATE_STRING = "ClientModeActiveState";
52    private static final String SCAN_ONLY_MODE_STATE_STRING = "ScanOnlyModeActiveState";
53    private static final String WIFI_DISABLED_STATE_STRING = "WifiDisabledState";
54    private static final String WIFI_IFACE_NAME = "mockWlan";
55
56    @Mock WifiInjector mWifiInjector;
57    @Mock Context mContext;
58    @Mock WifiNative mWifiNative;
59    @Mock WifiApConfigStore mWifiApConfigStore;
60    TestLooper mLooper;
61    @Mock ClientModeManager mClientModeManager;
62    @Mock ScanOnlyModeManager mScanOnlyModeManager;
63    @Mock SoftApManager mSoftApManager;
64    @Mock DefaultModeManager mDefaultModeManager;
65    @Mock IBatteryStats mBatteryStats;
66    @Mock SelfRecovery mSelfRecovery;
67    @Mock BaseWifiDiagnostics mWifiDiagnostics;
68    @Mock ScanRequestProxy mScanRequestProxy;
69    ClientModeManager.Listener mClientListener;
70    ScanOnlyModeManager.Listener mScanOnlyListener;
71    ScanOnlyModeCallback mScanOnlyCallback = new ScanOnlyModeCallback();
72    ClientModeCallback mClientModeCallback = new ClientModeCallback();
73    WifiManager.SoftApCallback mSoftApManagerCallback;
74    @Mock WifiManager.SoftApCallback mSoftApStateMachineCallback;
75    WifiNative.StatusListener mWifiNativeStatusListener;
76    WifiStateMachinePrime mWifiStateMachinePrime;
77
78    final ArgumentCaptor<WifiNative.StatusListener> mStatusListenerCaptor =
79            ArgumentCaptor.forClass(WifiNative.StatusListener.class);
80
81    /**
82     * Set up the test environment.
83     */
84    @Before
85    public void setUp() throws Exception {
86        Log.d(TAG, "Setting up ...");
87
88        MockitoAnnotations.initMocks(this);
89        mLooper = new TestLooper();
90
91        when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
92        when(mWifiInjector.getWifiDiagnostics()).thenReturn(mWifiDiagnostics);
93        when(mWifiInjector.getScanRequestProxy()).thenReturn(mScanRequestProxy);
94
95        mWifiStateMachinePrime = createWifiStateMachinePrime();
96        mLooper.dispatchAll();
97
98        verify(mWifiNative).registerStatusListener(mStatusListenerCaptor.capture());
99        mWifiNativeStatusListener = mStatusListenerCaptor.getValue();
100
101        mWifiStateMachinePrime.registerSoftApCallback(mSoftApStateMachineCallback);
102        mWifiStateMachinePrime.registerScanOnlyCallback(mScanOnlyCallback);
103        mWifiStateMachinePrime.registerClientModeCallback(mClientModeCallback);
104    }
105
106    private WifiStateMachinePrime createWifiStateMachinePrime() {
107        return new WifiStateMachinePrime(mWifiInjector,
108                                         mContext,
109                                         mLooper.getLooper(),
110                                         mWifiNative,
111                                         mDefaultModeManager,
112                                         mBatteryStats);
113    }
114
115    /**
116     * Clean up after tests - explicitly set tested object to null.
117     */
118    @After
119    public void cleanUp() throws Exception {
120        mWifiStateMachinePrime = null;
121    }
122
123    private class ClientModeCallback implements ClientModeManager.Listener {
124        public int currentState = WifiManager.WIFI_STATE_UNKNOWN;
125
126        @Override
127        public void onStateChanged(int state) {
128            currentState = state;
129        }
130    }
131
132    private class ScanOnlyModeCallback implements ScanOnlyModeManager.Listener {
133        public int currentState = WifiManager.WIFI_STATE_UNKNOWN;
134
135        @Override
136        public void onStateChanged(int state) {
137            currentState = state;
138        }
139    }
140
141    private void enterSoftApActiveMode() throws Exception {
142        enterSoftApActiveMode(
143                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
144    }
145
146    /**
147     * Helper method to enter the ClientModeActiveState for WifiStateMachinePrime.
148     */
149    private void enterClientModeActiveState() throws Exception {
150        String fromState = mWifiStateMachinePrime.getCurrentMode();
151        doAnswer(
152                new Answer<Object>() {
153                        public ClientModeManager answer(InvocationOnMock invocation) {
154                            Object[] args = invocation.getArguments();
155                            mClientListener = (ClientModeManager.Listener) args[0];
156                            return mClientModeManager;
157                        }
158                }).when(mWifiInjector).makeClientModeManager(
159                        any(ClientModeManager.Listener.class));
160        mWifiStateMachinePrime.enterClientMode();
161        mLooper.dispatchAll();
162        mClientListener.onStateChanged(WifiManager.WIFI_STATE_ENABLED);
163        mLooper.dispatchAll();
164
165        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
166        verify(mClientModeManager).start();
167        verify(mBatteryStats).noteWifiOn();
168    }
169
170    /**
171     * Helper method to enter the ScanOnlyModeActiveState for WifiStateMachinePrime.
172     */
173    private void enterScanOnlyModeActiveState() throws Exception {
174        String fromState = mWifiStateMachinePrime.getCurrentMode();
175        doAnswer(
176                new Answer<Object>() {
177                        public ScanOnlyModeManager answer(InvocationOnMock invocation) {
178                            Object[] args = invocation.getArguments();
179                            mScanOnlyListener = (ScanOnlyModeManager.Listener) args[0];
180                            return mScanOnlyModeManager;
181                        }
182                }).when(mWifiInjector).makeScanOnlyModeManager(
183                        any(ScanOnlyModeManager.Listener.class));
184        mWifiStateMachinePrime.enterScanOnlyMode();
185        mLooper.dispatchAll();
186        mScanOnlyListener.onStateChanged(WifiManager.WIFI_STATE_ENABLED);
187        mLooper.dispatchAll();
188
189        assertEquals(SCAN_ONLY_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
190        verify(mScanOnlyModeManager).start();
191        verify(mBatteryStats).noteWifiOn();
192        verify(mBatteryStats).noteWifiState(eq(BatteryStats.WIFI_STATE_OFF_SCANNING), eq(null));
193    }
194
195    /**
196     * Helper method to enter the SoftApActiveMode for WifiStateMachinePrime.
197     *
198     * This method puts the test object into the correct state and verifies steps along the way.
199     */
200    private void enterSoftApActiveMode(SoftApModeConfiguration softApConfig) throws Exception {
201        String fromState = mWifiStateMachinePrime.getCurrentMode();
202        doAnswer(
203                new Answer<Object>() {
204                    public SoftApManager answer(InvocationOnMock invocation) {
205                        Object[] args = invocation.getArguments();
206                        mSoftApManagerCallback = (WifiManager.SoftApCallback) args[0];
207                        assertEquals(softApConfig, (SoftApModeConfiguration) args[1]);
208                        return mSoftApManager;
209                    }
210                }).when(mWifiInjector).makeSoftApManager(any(WifiManager.SoftApCallback.class),
211                                                         any());
212        mWifiStateMachinePrime.enterSoftAPMode(softApConfig);
213        mLooper.dispatchAll();
214        verify(mSoftApManager).start();
215        if (fromState.equals(WIFI_DISABLED_STATE_STRING)) {
216            verify(mBatteryStats).noteWifiOn();
217        }
218    }
219
220    /**
221     * Test that after starting up, WSMP is in the Disabled State.
222     */
223    @Test
224    public void testWifiDisabledAtStartup() throws Exception {
225        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
226    }
227
228    /**
229     * Test that WifiStateMachinePrime properly enters the ScanOnlyModeActiveState from the
230     * WifiDisabled state.
231     */
232    @Test
233    public void testEnterScanOnlyModeFromDisabled() throws Exception {
234        enterScanOnlyModeActiveState();
235    }
236
237    /**
238     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from the
239     * WifiDisabled state.
240     */
241    @Test
242    public void testEnterSoftApModeFromDisabled() throws Exception {
243        enterSoftApActiveMode();
244    }
245
246    /**
247     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state.
248     */
249    @Test
250    public void testEnterSoftApModeFromDifferentState() throws Exception {
251        enterClientModeActiveState();
252        mLooper.dispatchAll();
253        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
254        reset(mBatteryStats);
255        enterSoftApActiveMode();
256    }
257
258    /**
259     * Test that we can disable wifi fully from the ScanOnlyModeActiveState.
260     */
261    @Test
262    public void testDisableWifiFromScanOnlyModeActiveState() throws Exception {
263        enterScanOnlyModeActiveState();
264
265        mWifiStateMachinePrime.disableWifi();
266        mLooper.dispatchAll();
267        verify(mScanOnlyModeManager).stop();
268        verify(mBatteryStats).noteWifiOff();
269        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
270    }
271
272    /**
273     * Test that we can disable wifi from the SoftApModeActiveState and not impact softap.
274     */
275    @Test
276    public void testDisableWifiFromSoftApModeActiveStateDoesNotStopSoftAp() throws Exception {
277        enterSoftApActiveMode();
278
279        reset(mDefaultModeManager);
280        mWifiStateMachinePrime.disableWifi();
281        mLooper.dispatchAll();
282        verify(mSoftApManager, never()).stop();
283        verify(mBatteryStats, never()).noteWifiOff();
284        verify(mDefaultModeManager).sendScanAvailableBroadcast(eq(mContext), eq(false));
285        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
286    }
287
288    /**
289     * Thest that we can switch from ScanOnlyActiveMode to another mode.
290     * Expectation: When switching out of ScanOlyModeActivState we stop the ScanOnlyModeManager.
291     */
292    @Test
293    public void testSwitchModeWhenScanOnlyModeActiveState() throws Exception {
294        enterScanOnlyModeActiveState();
295
296        reset(mBatteryStats);
297        enterClientModeActiveState();
298        mLooper.dispatchAll();
299        verify(mScanOnlyModeManager).stop();
300        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
301    }
302
303    /**
304     * Test that we can switch from SoftApActiveMode to another mode.
305     * Expectation: When switching out of SoftApModeActiveState we do not impact softap operation
306     */
307    @Test
308    public void testSwitchModeWhenSoftApActiveMode() throws Exception {
309        enterSoftApActiveMode();
310
311        reset(mWifiNative);
312
313        enterClientModeActiveState();
314        mLooper.dispatchAll();
315        verify(mSoftApManager, never()).stop();
316        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
317        verify(mWifiNative, never()).teardownAllInterfaces();
318    }
319
320    /**
321     * Test that we do enter the SoftApModeActiveState if we are already in WifiDisabledState due to
322     * a failure.
323     * Expectations: We should exit the current WifiDisabledState and re-enter before successfully
324     * entering the SoftApModeActiveState.
325     */
326    @Test
327    public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
328        enterSoftApActiveMode();
329        // now inject failure through the SoftApManager.Listener
330        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
331        mLooper.dispatchAll();
332        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
333        // clear the first call to start SoftApManager
334        reset(mSoftApManager, mBatteryStats);
335
336        enterSoftApActiveMode();
337    }
338
339    /**
340     * Test that we return to the WifiDisabledState after a failure is reported when in the
341     * ScanOnlyModeActiveState.
342     * Expectations: we should exit the ScanOnlyModeActiveState and stop the ScanOnlyModeManager.
343     */
344    @Test
345    public void testScanOnlyModeFailureWhenActive() throws Exception {
346        enterScanOnlyModeActiveState();
347        // now inject a failure through the ScanOnlyModeManager.Listener
348        mScanOnlyListener.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
349        mLooper.dispatchAll();
350        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
351        verify(mScanOnlyModeManager).stop();
352        verify(mBatteryStats).noteWifiOff();
353        assertEquals(WifiManager.WIFI_STATE_UNKNOWN, mScanOnlyCallback.currentState);
354    }
355
356    /**
357     * Test that we return to the WifiDisabledState after a failure is reported when in the
358     * SoftApModeActiveState.
359     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
360     */
361    @Test
362    public void testSoftApFailureWhenActive() throws Exception {
363        enterSoftApActiveMode();
364        // now inject failure through the SoftApManager.Listener
365        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
366        mLooper.dispatchAll();
367        verify(mBatteryStats).noteWifiOff();
368    }
369
370    /**
371     * Test that we return to the WifiDisabledState after the ScanOnlyModeManager is stopping in the
372     * ScanOnlyModeActiveState.
373     * Expectations: We should exit the ScanOnlyModeActiveState and stop the ScanOnlyModeManager.
374     */
375    @Test
376    public void testScanOnlyModeDisabledWhenActive() throws Exception {
377        enterScanOnlyModeActiveState();
378        // now inject the stop message through the ScanOnlyModeManager.Listener
379        mScanOnlyListener.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
380        mLooper.dispatchAll();
381        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
382        verify(mScanOnlyModeManager).stop();
383        verify(mBatteryStats).noteWifiOff();
384    }
385
386    /**
387     * Test that we return to the WifiDisabledState after the SoftApManager is stopped in the
388     * SoftApModeActiveState.
389     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
390     */
391    @Test
392    public void testSoftApDisabledWhenActive() throws Exception {
393        enterSoftApActiveMode();
394        reset(mWifiNative);
395        // now inject failure through the SoftApManager.Listener
396        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
397        mLooper.dispatchAll();
398        verify(mBatteryStats).noteWifiOff();
399        verifyNoMoreInteractions(mWifiNative);
400    }
401
402    /**
403     * Verifies that SoftApStateChanged event is being passed from SoftApManager to WifiServiceImpl
404     */
405    @Test
406    public void callsWifiServiceCallbackOnSoftApStateChanged() throws Exception {
407        enterSoftApActiveMode();
408
409        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
410        mLooper.dispatchAll();
411
412        verify(mSoftApStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
413    }
414
415    /**
416     * Verifies that triggering a state change update will not crash if the callback to
417     * WifiServiceImpl is null.
418     */
419    @Test
420    public void testNullCallbackToWifiServiceImplForStateChange() throws Exception {
421        //set the callback to null
422        mWifiStateMachinePrime.registerSoftApCallback(null);
423
424        enterSoftApActiveMode();
425
426        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
427        mLooper.dispatchAll();
428
429        verify(mSoftApStateMachineCallback, never()).onStateChanged(anyInt(), anyInt());
430    }
431
432    /**
433     * Verifies that NumClientsChanged event is being passed from SoftApManager to WifiServiceImpl
434     */
435    @Test
436    public void callsWifiServiceCallbackOnSoftApNumClientsChanged() throws Exception {
437        final int testNumClients = 3;
438        enterSoftApActiveMode();
439        mSoftApManagerCallback.onNumClientsChanged(testNumClients);
440        mLooper.dispatchAll();
441
442        verify(mSoftApStateMachineCallback).onNumClientsChanged(testNumClients);
443    }
444
445    /**
446     * Verifies that triggering a number of clients changed update will not crash if the callback to
447     * WifiServiceImpl is null.
448     */
449    @Test
450    public void testNullCallbackToWifiServiceImplForNumClientsChanged() throws Exception {
451
452        final int testNumClients = 3;
453
454        //set the callback to null
455        mWifiStateMachinePrime.registerSoftApCallback(null);
456
457        enterSoftApActiveMode();
458        mSoftApManagerCallback.onNumClientsChanged(testNumClients);
459
460        verify(mSoftApStateMachineCallback, never()).onNumClientsChanged(anyInt());
461    }
462
463    /**
464     * Test that we remain in the active state when we get a state change update that scan mode is
465     * active.
466     * Expectations: We should remain in the ScanOnlyModeActive state.
467     */
468    @Test
469    public void testScanOnlyModeStaysActiveOnEnabledUpdate() throws Exception {
470        enterScanOnlyModeActiveState();
471        // now inject failure through the SoftApManager.Listener
472        mScanOnlyListener.onStateChanged(WifiManager.WIFI_STATE_ENABLED);
473        mLooper.dispatchAll();
474        assertEquals(SCAN_ONLY_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
475        verify(mScanOnlyModeManager, never()).stop();
476    }
477
478    /**
479     * Test that we do not act on unepected state string messages and remain in the active state.
480     * Expectations: We should remain in the ScanOnlyModeActive state.
481     */
482    @Test
483    public void testScanOnlyModeStaysActiveOnUnexpectedStateUpdate() throws Exception {
484        enterScanOnlyModeActiveState();
485        // now inject failure through the SoftApManager.Listener
486        mScanOnlyListener.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING);
487        mLooper.dispatchAll();
488        assertEquals(SCAN_ONLY_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
489        verify(mScanOnlyModeManager, never()).stop();
490    }
491
492    /**
493     * Test that a config passed in to the call to enterSoftApMode is used to create the new
494     * SoftApManager.
495     * Expectations: We should create a SoftApManager in WifiInjector with the config passed in to
496     * WifiStateMachinePrime to switch to SoftApMode.
497     */
498    @Test
499    public void testConfigIsPassedToWifiInjector() throws Exception {
500        WifiConfiguration config = new WifiConfiguration();
501        config.SSID = "ThisIsAConfig";
502        SoftApModeConfiguration softApConfig =
503                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
504        enterSoftApActiveMode(softApConfig);
505    }
506
507    /**
508     * Test that when enterSoftAPMode is called with a null config, we pass a null config to
509     * WifiInjector.makeSoftApManager.
510     *
511     * Passing a null config to SoftApManager indicates that the default config should be used.
512     *
513     * Expectations: WifiInjector should be called with a null config.
514     */
515    @Test
516    public void testNullConfigIsPassedToWifiInjector() throws Exception {
517        enterSoftApActiveMode();
518    }
519
520    /**
521     * Test that two calls to switch to SoftAPMode in succession ends up with the correct config.
522     *
523     * Expectation: we should end up in SoftAPMode state configured with the second config.
524     */
525    @Test
526    public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
527        when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
528        WifiConfiguration config1 = new WifiConfiguration();
529        config1.SSID = "ThisIsAConfig";
530        SoftApModeConfiguration softApConfig1 =
531                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config1);
532        WifiConfiguration config2 = new WifiConfiguration();
533        config2.SSID = "ThisIsASecondConfig";
534        SoftApModeConfiguration softApConfig2 =
535                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config2);
536
537        when(mWifiInjector.makeSoftApManager(any(WifiManager.SoftApCallback.class),
538                                             eq(softApConfig1)))
539                .thenReturn(mSoftApManager);
540        // make a second softap manager
541        SoftApManager softapManager = mock(SoftApManager.class);
542        when(mWifiInjector.makeSoftApManager(any(WifiManager.SoftApCallback.class),
543                                             eq(softApConfig2)))
544                .thenReturn(softapManager);
545
546        mWifiStateMachinePrime.enterSoftAPMode(softApConfig1);
547        mWifiStateMachinePrime.enterSoftAPMode(softApConfig2);
548        mLooper.dispatchAll();
549        verify(mSoftApManager).start();
550        verify(softapManager).start();
551        verify(mBatteryStats).noteWifiOn();
552    }
553
554    /**
555     * Test that we safely disable wifi if it is already disabled.
556     * Expectations: We should not interact with WifiNative since we should have already cleaned up
557     * everything.
558     */
559    @Test
560    public void disableWifiWhenAlreadyOff() throws Exception {
561        mWifiStateMachinePrime.disableWifi();
562        // since we start up in disabled, this should not re-enter the disabled state
563        verify(mDefaultModeManager).sendScanAvailableBroadcast(eq(mContext), eq(false));
564    }
565
566    /**
567     * Trigger recovery and a bug report if we see a native failure.
568     */
569    @Test
570    public void handleWifiNativeFailure() throws Exception {
571        mWifiNativeStatusListener.onStatusChanged(false);
572        mLooper.dispatchAll();
573        verify(mWifiDiagnostics).captureBugReportData(
574                WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
575        verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_WIFINATIVE_FAILURE));
576    }
577
578    /**
579     * Verify an onStatusChanged callback with "true" does not trigger recovery.
580     */
581    @Test
582    public void handleWifiNativeStatusReady() throws Exception {
583        mWifiNativeStatusListener.onStatusChanged(true);
584        mLooper.dispatchAll();
585        verify(mWifiDiagnostics, never()).captureBugReportData(
586                WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
587        verify(mSelfRecovery, never()).trigger(eq(SelfRecovery.REASON_WIFINATIVE_FAILURE));
588    }
589
590    /**
591     * Verify that mode stop is safe even if the underlying Client mode exited already.
592     */
593    @Test
594    public void shutdownWifiDoesNotCrashWhenClientModeExitsOnDestroyed() throws Exception {
595        enterClientModeActiveState();
596
597        mClientListener.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
598        mLooper.dispatchAll();
599
600        mWifiStateMachinePrime.shutdownWifi();
601
602        assertEquals(WifiManager.WIFI_STATE_DISABLED, mClientModeCallback.currentState);
603    }
604
605    /**
606     * Verify that an interface destruction callback is safe after already having been stopped.
607     */
608    @Test
609    public void onDestroyedCallbackDoesNotCrashWhenClientModeAlreadyStopped() throws Exception {
610        enterClientModeActiveState();
611
612        mWifiStateMachinePrime.shutdownWifi();
613
614        mClientListener.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
615        mLooper.dispatchAll();
616
617        assertEquals(WifiManager.WIFI_STATE_DISABLED, mClientModeCallback.currentState);
618    }
619
620    /**
621     * Verify that mode stop is safe even if the underlying softap mode exited already.
622     */
623    @Test
624    public void shutdownWifiDoesNotCrashWhenSoftApExitsOnDestroyed() throws Exception {
625        enterSoftApActiveMode();
626
627        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
628        mLooper.dispatchAll();
629
630        mWifiStateMachinePrime.shutdownWifi();
631
632        verify(mSoftApStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
633    }
634
635    /**
636     * Verify that an interface destruction callback is safe after already having been stopped.
637     */
638    @Test
639    public void onDestroyedCallbackDoesNotCrashWhenSoftApModeAlreadyStopped() throws Exception {
640        enterSoftApActiveMode();
641
642        mWifiStateMachinePrime.shutdownWifi();
643
644        mSoftApManagerCallback.onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
645        mLooper.dispatchAll();
646
647        verify(mSoftApStateMachineCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
648    }
649
650    /**
651     * Verify that we do not crash when calling dump and wifi is fully disabled.
652     */
653    @Test
654    public void dumpWhenWifiFullyOffDoesNotCrash() throws Exception {
655        ByteArrayOutputStream stream = new ByteArrayOutputStream();
656        PrintWriter writer = new PrintWriter(stream);
657        mWifiStateMachinePrime.dump(null, writer, null);
658    }
659
660    /**
661     * Verify that we trigger dump on active mode managers.
662     */
663    @Test
664    public void dumpCallsActiveModeManagers() throws Exception {
665        enterSoftApActiveMode();
666        enterClientModeActiveState();
667        enterScanOnlyModeActiveState();
668
669        ByteArrayOutputStream stream = new ByteArrayOutputStream();
670        PrintWriter writer = new PrintWriter(stream);
671        mWifiStateMachinePrime.dump(null, writer, null);
672
673        verify(mSoftApManager).dump(eq(null), eq(writer), eq(null));
674        // can only be in scan or client, so we should not have a client mode active
675        verify(mClientModeManager, never()).dump(eq(null), eq(writer), eq(null));
676        verify(mScanOnlyModeManager).dump(eq(null), eq(writer), eq(null));
677    }
678}
679