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.net.wifi.IApInterface;
23import android.net.wifi.IWificond;
24import android.net.wifi.WifiConfiguration;
25import android.net.wifi.WifiManager;
26import android.os.INetworkManagementService;
27import android.os.test.TestLooper;
28import android.test.suitebuilder.annotation.SmallTest;
29import android.util.Log;
30
31import org.junit.After;
32import org.junit.Before;
33import org.junit.Test;
34import org.mockito.Mock;
35import org.mockito.MockitoAnnotations;
36import org.mockito.invocation.InvocationOnMock;
37import org.mockito.stubbing.Answer;
38
39/**
40 * Unit tests for {@link com.android.server.wifi.WifiStateMachinePrime}.
41 */
42@SmallTest
43public class WifiStateMachinePrimeTest {
44    public static final String TAG = "WifiStateMachinePrimeTest";
45
46    private static final String CLIENT_MODE_STATE_STRING = "ClientModeState";
47    private static final String SCAN_ONLY_MODE_STATE_STRING = "ScanOnlyModeState";
48    private static final String SOFT_AP_MODE_STATE_STRING = "SoftAPModeState";
49    private static final String WIFI_DISABLED_STATE_STRING = "WifiDisabledState";
50    private static final String CLIENT_MODE_ACTIVE_STATE_STRING = "ClientModeActiveState";
51    private static final String SCAN_ONLY_MODE_ACTIVE_STATE_STRING = "ScanOnlyModeActiveState";
52    private static final String SOFT_AP_MODE_ACTIVE_STATE_STRING = "SoftAPModeActiveState";
53
54    @Mock WifiInjector mWifiInjector;
55    @Mock WifiApConfigStore mWifiApConfigStore;
56    TestLooper mLooper;
57    @Mock IWificond mWificond;
58    @Mock IApInterface mApInterface;
59    @Mock INetworkManagementService mNMService;
60    @Mock SoftApManager mSoftApManager;
61    SoftApManager.Listener mSoftApListener;
62    WifiStateMachinePrime mWifiStateMachinePrime;
63
64    /**
65     * Set up the test environment.
66     */
67    @Before
68    public void setUp() throws Exception {
69        Log.d(TAG, "Setting up ...");
70
71        MockitoAnnotations.initMocks(this);
72        mLooper = new TestLooper();
73
74        mWifiInjector = mock(WifiInjector.class);
75        mWifiStateMachinePrime = createWifiStateMachinePrime();
76    }
77
78    private WifiStateMachinePrime createWifiStateMachinePrime() {
79        when(mWifiInjector.makeWificond()).thenReturn(null);
80        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
81    }
82
83    /**
84     * Clean up after tests - explicitly set tested object to null.
85     */
86    @After
87    public void cleanUp() throws Exception {
88        mWifiStateMachinePrime = null;
89    }
90
91    private void enterSoftApActiveMode() throws Exception {
92        enterSoftApActiveMode(null);
93    }
94
95    /**
96     * Helper method to enter the SoftApActiveMode for WifiStateMachinePrime.
97     *
98     * This method puts the test object into the correct state and verifies steps along the way.
99     */
100    private void enterSoftApActiveMode(WifiConfiguration wifiConfig) throws Exception {
101        String fromState = mWifiStateMachinePrime.getCurrentMode();
102        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
103        when(mWificond.createApInterface()).thenReturn(mApInterface);
104        doAnswer(
105                new Answer<Object>() {
106                    public SoftApManager answer(InvocationOnMock invocation) {
107                        Object[] args = invocation.getArguments();
108                        assertEquals(mNMService, (INetworkManagementService) args[0]);
109                        mSoftApListener = (SoftApManager.Listener) args[1];
110                        assertEquals(mApInterface, (IApInterface) args[2]);
111                        assertEquals(wifiConfig, (WifiConfiguration) args[3]);
112                        return mSoftApManager;
113                    }
114                }).when(mWifiInjector).makeSoftApManager(any(INetworkManagementService.class),
115                                                         any(SoftApManager.Listener.class),
116                                                         any(IApInterface.class),
117                                                         any());
118        mWifiStateMachinePrime.enterSoftAPMode(wifiConfig);
119        mLooper.dispatchAll();
120        Log.e("WifiStateMachinePrimeTest", "check fromState: " + fromState);
121        if (!fromState.equals(WIFI_DISABLED_STATE_STRING)) {
122            verify(mWificond).tearDownInterfaces();
123        }
124        assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
125        verify(mSoftApManager).start();
126    }
127
128    /**
129     * Test that when a new instance of WifiStateMachinePrime is created, any existing interfaces in
130     * the retrieved Wificond instance are cleaned up.
131     * Expectations:  When the new WifiStateMachinePrime instance is created a call to
132     * Wificond.tearDownInterfaces() is made.
133     */
134    @Test
135    public void testWificondExistsOnStartup() throws Exception {
136        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
137        WifiStateMachinePrime testWifiStateMachinePrime =
138                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
139        verify(mWificond).tearDownInterfaces();
140    }
141
142    /**
143     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from the
144     * WifiDisabled state.
145     */
146    @Test
147    public void testEnterSoftApModeFromDisabled() throws Exception {
148        enterSoftApActiveMode();
149    }
150
151    /**
152     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state.
153     * Expectations: When going from one state to another, any interfaces that are still up are torn
154     * down.
155     */
156    @Test
157    public void testEnterSoftApModeFromDifferentState() throws Exception {
158        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
159        mWifiStateMachinePrime.enterClientMode();
160        mLooper.dispatchAll();
161        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
162        enterSoftApActiveMode();
163    }
164
165    /**
166     * Test that we can disable wifi fully from the SoftApModeActiveState.
167     */
168    @Test
169    public void testDisableWifiFromSoftApModeActiveState() throws Exception {
170        enterSoftApActiveMode();
171
172        mWifiStateMachinePrime.disableWifi();
173        mLooper.dispatchAll();
174        verify(mSoftApManager).stop();
175        verify(mWificond).tearDownInterfaces();
176        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
177    }
178
179    /**
180     * Test that we can disable wifi fully from the SoftApModeState.
181     */
182    @Test
183    public void testDisableWifiFromSoftApModeState() throws Exception {
184        // Use a failure getting wificond to stay in the SoftAPModeState
185        when(mWifiInjector.makeWificond()).thenReturn(null);
186        mWifiStateMachinePrime.enterSoftAPMode(null);
187        mLooper.dispatchAll();
188        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
189
190        mWifiStateMachinePrime.disableWifi();
191        mLooper.dispatchAll();
192        // mWificond will be null due to this test, no call to tearDownInterfaces here.
193        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
194    }
195
196    /**
197     * Test that we can switch from SoftApActiveMode to another mode.
198     * Expectation: When switching out of SoftApModeActiveState we stop the SoftApManager and tear
199     * down existing interfaces.
200     */
201    @Test
202    public void testSwitchModeWhenSoftApActiveMode() throws Exception {
203        enterSoftApActiveMode();
204
205        mWifiStateMachinePrime.enterClientMode();
206        mLooper.dispatchAll();
207        verify(mSoftApManager).stop();
208        verify(mWificond).tearDownInterfaces();
209        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
210    }
211
212    /**
213     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get a reference to
214     * wificond.
215     * Expectations: After a failed attempt to get wificond from WifiInjector, we should remain in
216     * the SoftApModeState.
217     */
218    @Test
219    public void testWificondNullWhenSwitchingToApMode() throws Exception {
220        when(mWifiInjector.makeWificond()).thenReturn(null);
221        mWifiStateMachinePrime.enterSoftAPMode(null);
222        mLooper.dispatchAll();
223        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
224    }
225
226    /**
227     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get an ApInterface
228     * from wificond.
229     * Expectations: After a failed attempt to get an ApInterface from WifiInjector, we should
230     * remain in the SoftApModeState.
231     */
232    @Test
233    public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception {
234        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
235        when(mWificond.createApInterface()).thenReturn(null);
236        mWifiStateMachinePrime.enterSoftAPMode(null);
237        mLooper.dispatchAll();
238        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
239    }
240
241    /**
242     * Test that we do can enter the SoftApModeActiveState if we are already in the SoftApModeState.
243     * Expectations: We should exit the current SoftApModeState and re-enter before successfully
244     * entering the SoftApModeActiveState.
245     */
246    @Test
247    public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
248        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
249        when(mWificond.createApInterface()).thenReturn(null);
250        mWifiStateMachinePrime.enterSoftAPMode(null);
251        mLooper.dispatchAll();
252        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
253
254        enterSoftApActiveMode();
255    }
256
257    /**
258     * Test that we return to the SoftApModeState after a failure is reported when in the
259     * SoftApModeActiveState.
260     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
261     */
262    @Test
263    public void testSoftApFailureWhenActive() throws Exception {
264        enterSoftApActiveMode();
265        // now inject failure through the SoftApManager.Listener
266        mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
267        mLooper.dispatchAll();
268        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
269        verify(mSoftApManager).stop();
270    }
271
272    /**
273     * Test that we return to the SoftApModeState after the SoftApManager is stopped in the
274     * SoftApModeActiveState.
275     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
276     */
277    @Test
278    public void testSoftApDisabledWhenActive() throws Exception {
279        enterSoftApActiveMode();
280        // now inject failure through the SoftApManager.Listener
281        mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
282        mLooper.dispatchAll();
283        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
284        verify(mSoftApManager).stop();
285    }
286
287    /**
288     * Test that a config passed in to the call to enterSoftApMode is used to create the new
289     * SoftApManager.
290     * Expectations: We should create a SoftApManager in WifiInjector with the config passed in to
291     * WifiStateMachinePrime to switch to SoftApMode.
292     */
293    @Test
294    public void testConfigIsPassedToWifiInjector() throws Exception {
295        WifiConfiguration config = new WifiConfiguration();
296        config.SSID = "ThisIsAConfig";
297        enterSoftApActiveMode(config);
298    }
299
300    /**
301     * Test that when enterSoftAPMode is called with a null config, we pass a null config to
302     * WifiInjector.makeSoftApManager.
303     *
304     * Passing a null config to SoftApManager indicates that the default config should be used.
305     *
306     * Expectations: WifiInjector should be called with a null config.
307     */
308    @Test
309    public void testNullConfigIsPassedToWifiInjector() throws Exception {
310        enterSoftApActiveMode(null);
311    }
312
313    /**
314     * Test that the proper config is used if a prior attempt fails without using the config.
315     * Expectations: A call to start softap with a null config fails, but a second call has a set
316     * config - this second call should use the correct config.
317     */
318    @Test
319    public void testNullConfigFailsSecondCallWithConfigSuccessful() throws Exception {
320        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
321        when(mWificond.createApInterface()).thenReturn(null);
322        mWifiStateMachinePrime.enterSoftAPMode(null);
323        mLooper.dispatchAll();
324        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
325        WifiConfiguration config = new WifiConfiguration();
326        config.SSID = "ThisIsAConfig";
327        enterSoftApActiveMode(config);
328    }
329
330    /**
331     * Test that a failed call to start softap with a valid config has the config saved for future
332     * calls to enable softap.
333     *
334     * Expectations: A call to start SoftAPMode with a config should write out the config if we
335     * did not create a SoftApManager.
336     */
337    @Test
338    public void testValidConfigIsSavedOnFailureToStart() throws Exception {
339        when(mWifiInjector.makeWificond()).thenReturn(null);
340        when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
341        WifiConfiguration config = new WifiConfiguration();
342        config.SSID = "ThisIsAConfig";
343        mWifiStateMachinePrime.enterSoftAPMode(config);
344        mLooper.dispatchAll();
345        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
346        verify(mWifiApConfigStore).setApConfiguration(eq(config));
347    }
348
349    /**
350     * Thest that two calls to switch to SoftAPMode in succession ends up with the correct config.
351     *
352     * Expectation: we should end up in SoftAPMode state configured with the second config.
353     */
354    @Test
355    public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
356        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
357        when(mWificond.createApInterface()).thenReturn(mApInterface);
358        when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
359        WifiConfiguration config1 = new WifiConfiguration();
360        config1.SSID = "ThisIsAConfig";
361        WifiConfiguration config2 = new WifiConfiguration();
362        config2.SSID = "ThisIsASecondConfig";
363
364        when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
365                                             any(SoftApManager.Listener.class),
366                                             any(IApInterface.class),
367                                             eq(config1)))
368                .thenReturn(mSoftApManager);
369        when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
370                                             any(SoftApManager.Listener.class),
371                                             any(IApInterface.class),
372                                             eq(config2)))
373                .thenReturn(mSoftApManager);
374
375
376        mWifiStateMachinePrime.enterSoftAPMode(config1);
377        mWifiStateMachinePrime.enterSoftAPMode(config2);
378        mLooper.dispatchAll();
379        assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
380    }
381
382    /**
383     * Test that we safely disable wifi if it is already disabled.
384     * Expectations: We should not interact with wificond since we should have already cleaned up
385     * everything.
386     */
387    @Test
388    public void disableWifiWhenAlreadyOff() throws Exception {
389        verifyNoMoreInteractions(mWificond);
390        mWifiStateMachinePrime.disableWifi();
391    }
392}
393