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 android.net.wifi.WifiManager.WIFI_MODE_FULL;
20
21import static com.android.server.wifi.WifiController.CMD_AP_STOPPED;
22import static com.android.server.wifi.WifiController.CMD_DEVICE_IDLE;
23import static com.android.server.wifi.WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED;
24import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
25import static com.android.server.wifi.WifiController.CMD_RESTART_WIFI;
26import static com.android.server.wifi.WifiController.CMD_SET_AP;
27import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
28
29import static org.junit.Assert.assertEquals;
30import static org.mockito.Mockito.*;
31
32import android.content.ContentResolver;
33import android.content.Context;
34import android.database.ContentObserver;
35import android.net.Uri;
36import android.os.WorkSource;
37import android.os.test.TestLooper;
38import android.test.suitebuilder.annotation.SmallTest;
39import android.util.Log;
40
41import com.android.internal.util.IState;
42import com.android.internal.util.StateMachine;
43
44import org.junit.After;
45import org.junit.Before;
46import org.junit.Test;
47import org.mockito.ArgumentCaptor;
48import org.mockito.InOrder;
49import org.mockito.Mock;
50import org.mockito.MockitoAnnotations;
51
52import java.io.ByteArrayOutputStream;
53import java.io.PrintWriter;
54import java.lang.reflect.Method;
55import java.util.List;
56
57/**
58 * Test WifiController for changes in and out of ECM and SoftAP modes.
59 */
60@SmallTest
61public class WifiControllerTest {
62
63    private static final String TAG = "WifiControllerTest";
64
65    private void dumpState() {
66        ByteArrayOutputStream stream = new ByteArrayOutputStream();
67        PrintWriter writer = new PrintWriter(stream);
68        mWifiController.dump(null, writer, null);
69        writer.flush();
70        Log.d(TAG, "WifiStateMachine state -" + stream.toString());
71    }
72
73    private IState getCurrentState() throws Exception {
74        Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
75        method.setAccessible(true);
76        return (IState) method.invoke(mWifiController);
77    }
78
79    private void initializeSettingsStore() throws Exception {
80        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
81        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
82        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
83    }
84
85    TestLooper mLooper;
86    @Mock Context mContext;
87    @Mock WifiServiceImpl mService;
88    @Mock FrameworkFacade mFacade;
89    @Mock WifiSettingsStore mSettingsStore;
90    @Mock WifiStateMachine mWifiStateMachine;
91    @Mock WifiLockManager mWifiLockManager;
92    @Mock ContentResolver mContentResolver;
93
94    ContentObserver mStayAwakeObserver;
95    ContentObserver mWifiIdleTimeObserver;
96    ContentObserver mWifiSleepPolicyObserver;
97
98    WifiController mWifiController;
99
100    @Before
101    public void setUp() throws Exception {
102        MockitoAnnotations.initMocks(this);
103
104        mLooper = new TestLooper();
105
106        initializeSettingsStore();
107
108        when(mContext.getContentResolver()).thenReturn(mContentResolver);
109        ArgumentCaptor<ContentObserver> observerCaptor =
110                ArgumentCaptor.forClass(ContentObserver.class);
111
112        mWifiController = new WifiController(mContext, mWifiStateMachine,
113                mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
114        verify(mFacade, times(3)).registerContentObserver(eq(mContext), any(Uri.class), eq(false),
115                observerCaptor.capture());
116
117        List<ContentObserver> observers = observerCaptor.getAllValues();
118        mStayAwakeObserver = observers.get(0);
119        mWifiIdleTimeObserver = observers.get(1);
120        mWifiSleepPolicyObserver = observers.get(2);
121
122        mWifiController.start();
123        mLooper.dispatchAll();
124    }
125
126    @After
127    public void cleanUp() {
128        mLooper.dispatchAll();
129    }
130
131    @Test
132    public void enableWifi() throws Exception {
133        assertEquals("StaDisabledWithScanState", getCurrentState().getName());
134
135        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
136        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
137        mLooper.dispatchAll();
138        assertEquals("DeviceActiveState", getCurrentState().getName());
139
140        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
141        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
142        mLooper.dispatchAll();
143        assertEquals("StaDisabledWithScanState", getCurrentState().getName());
144
145        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
146        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
147        mLooper.dispatchAll();
148        assertEquals("DeviceActiveState", getCurrentState().getName());
149    }
150
151    @Test
152    public void testEcmOn() throws Exception {
153        enableWifi();
154
155        // Test with WifiDisableInECBM turned on:
156        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
157        doTestEcm(true);
158    }
159
160    @Test
161    public void testEcmOff() throws Exception {
162        enableWifi();
163
164        // Test with WifiDisableInECBM turned off
165        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(false);
166        doTestEcm(false);
167    }
168
169    private void assertInEcm(boolean ecmEnabled) throws Exception {
170        if (ecmEnabled) {
171            assertEquals("EcmState", getCurrentState().getName());
172        } else {
173            assertEquals("DeviceActiveState", getCurrentState().getName());
174        }
175    }
176
177
178    private void doTestEcm(boolean ecmEnabled) throws Exception {
179
180        // test ecm changed
181        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 1);
182        mLooper.dispatchAll();
183        assertInEcm(ecmEnabled);
184
185        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 0);
186        mLooper.dispatchAll();
187        assertEquals("DeviceActiveState", getCurrentState().getName());
188
189        // test call state changed
190        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
191        mLooper.dispatchAll();
192        assertInEcm(ecmEnabled);
193
194        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 0);
195        mLooper.dispatchAll();
196        assertEquals("DeviceActiveState", getCurrentState().getName());
197
198
199        // test both changed (variation 1 - the good case)
200        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
201        mLooper.dispatchAll();
202        assertInEcm(ecmEnabled);
203
204        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 1);
205        mLooper.dispatchAll();
206        assertInEcm(ecmEnabled);
207
208        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 0);
209        mLooper.dispatchAll();
210        assertInEcm(ecmEnabled);
211
212        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 0);
213        mLooper.dispatchAll();
214        assertEquals("DeviceActiveState", getCurrentState().getName());
215
216        // test both changed (variation 2 - emergency call in ecm)
217        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 1);
218        mLooper.dispatchAll();
219        assertInEcm(ecmEnabled);
220
221        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
222        mLooper.dispatchAll();
223        assertInEcm(ecmEnabled);
224
225        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 0);
226        mLooper.dispatchAll();
227        assertInEcm(ecmEnabled);
228
229        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 0);
230        mLooper.dispatchAll();
231        assertEquals("DeviceActiveState", getCurrentState().getName());
232
233        // test both changed (variation 3 - not so good order of events)
234        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
235        mLooper.dispatchAll();
236        assertInEcm(ecmEnabled);
237
238        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 1);
239        mLooper.dispatchAll();
240        assertInEcm(ecmEnabled);
241
242        mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, 0);
243        mLooper.dispatchAll();
244        assertInEcm(ecmEnabled);
245
246        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 0);
247        mLooper.dispatchAll();
248        assertEquals("DeviceActiveState", getCurrentState().getName());
249
250        // test that Wifi toggle doesn't exit Ecm
251        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
252        mLooper.dispatchAll();
253        assertInEcm(ecmEnabled);
254
255        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
256        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
257        mLooper.dispatchAll();
258        assertInEcm(ecmEnabled);
259
260        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 0);
261        mLooper.dispatchAll();
262        assertEquals("DeviceActiveState", getCurrentState().getName());
263    }
264
265    /**
266     * When AP mode is enabled and wifi was previously in AP mode, we should return to
267     * DeviceActiveState after the AP is disabled.
268     * Enter DeviceActiveState, activate AP mode, disable AP mode.
269     * <p>
270     * Expected: AP should successfully start and exit, then return to DeviceActiveState.
271     */
272    @Test
273    public void testReturnToDeviceActiveStateAfterAPModeShutdown() throws Exception {
274        enableWifi();
275        assertEquals("DeviceActiveState", getCurrentState().getName());
276
277        mWifiController.obtainMessage(CMD_SET_AP, 1, 0).sendToTarget();
278        mLooper.dispatchAll();
279        assertEquals("ApEnabledState", getCurrentState().getName());
280
281        when(mSettingsStore.getWifiSavedState()).thenReturn(1);
282        mWifiController.obtainMessage(CMD_AP_STOPPED).sendToTarget();
283        mLooper.dispatchAll();
284
285        InOrder inOrder = inOrder(mWifiStateMachine);
286        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
287        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
288        assertEquals("DeviceActiveState", getCurrentState().getName());
289    }
290
291    /**
292     * When AP mode is enabled and wifi is toggled on, we should transition to
293     * DeviceActiveState after the AP is disabled.
294     * Enter DeviceActiveState, activate AP mode, toggle WiFi.
295     * <p>
296     * Expected: AP should successfully start and exit, then return to DeviceActiveState.
297     */
298    @Test
299    public void testReturnToDeviceActiveStateAfterWifiEnabledShutdown() throws Exception {
300        enableWifi();
301        assertEquals("DeviceActiveState", getCurrentState().getName());
302
303        mWifiController.obtainMessage(CMD_SET_AP, 1, 0).sendToTarget();
304        mLooper.dispatchAll();
305        assertEquals("ApEnabledState", getCurrentState().getName());
306
307        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
308        mWifiController.obtainMessage(CMD_WIFI_TOGGLED).sendToTarget();
309        mWifiController.obtainMessage(CMD_AP_STOPPED).sendToTarget();
310        mLooper.dispatchAll();
311
312        InOrder inOrder = inOrder(mWifiStateMachine);
313        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
314        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
315        assertEquals("DeviceActiveState", getCurrentState().getName());
316    }
317
318    /**
319     * When the wifi device is idle, AP mode is enabled and disabled
320     * we should return to the appropriate Idle state.
321     * Enter DeviceActiveState, indicate idle device, activate AP mode, disable AP mode.
322     * <p>
323     * Expected: AP should successfully start and exit, then return to a device idle state.
324     */
325    @Test
326    public void testReturnToDeviceIdleStateAfterAPModeShutdown() throws Exception {
327        enableWifi();
328        assertEquals("DeviceActiveState", getCurrentState().getName());
329
330        // make sure mDeviceIdle is set to true
331        when(mWifiLockManager.getStrongestLockMode()).thenReturn(WIFI_MODE_FULL);
332        when(mWifiLockManager.createMergedWorkSource()).thenReturn(new WorkSource());
333        mWifiController.sendMessage(CMD_DEVICE_IDLE);
334        mLooper.dispatchAll();
335        assertEquals("FullLockHeldState", getCurrentState().getName());
336
337        mWifiController.obtainMessage(CMD_SET_AP, 1, 0).sendToTarget();
338        mLooper.dispatchAll();
339        assertEquals("ApEnabledState", getCurrentState().getName());
340
341        when(mSettingsStore.getWifiSavedState()).thenReturn(1);
342        mWifiController.obtainMessage(CMD_AP_STOPPED).sendToTarget();
343        mLooper.dispatchAll();
344
345        InOrder inOrder = inOrder(mWifiStateMachine);
346        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
347        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
348        assertEquals("FullLockHeldState", getCurrentState().getName());
349    }
350
351    /**
352     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
353     * are not in STA mode.
354     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
355     * should be ignored.
356     * Create and start WifiController in ApStaDisabledState, send command to restart WiFi
357     * <p>
358     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
359     */
360    @Test
361    public void testRestartWifiStackInApStaDisabledState() throws Exception {
362        // Start a new WifiController with wifi disabled
363        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
364        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
365        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
366
367        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
368
369        mWifiController = new WifiController(mContext, mWifiStateMachine,
370                mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
371
372        mWifiController.start();
373        mLooper.dispatchAll();
374
375        reset(mWifiStateMachine);
376        assertEquals("ApStaDisabledState", getCurrentState().getName());
377        mWifiController.sendMessage(CMD_RESTART_WIFI);
378        mLooper.dispatchAll();
379        verifyZeroInteractions(mWifiStateMachine);
380    }
381
382    /**
383     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
384     * are not in STA mode, even if scans are allowed.
385     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
386     * should be ignored.
387     * Create and start WifiController in StaDisablediWithScanState, send command to restart WiFi
388     * <p>
389     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
390     */
391    @Test
392    public void testRestartWifiStackInStaDisabledWithScanState() throws Exception {
393        reset(mWifiStateMachine);
394        assertEquals("StaDisabledWithScanState", getCurrentState().getName());
395        mWifiController.sendMessage(CMD_RESTART_WIFI);
396        mLooper.dispatchAll();
397        verifyZeroInteractions(mWifiStateMachine);
398    }
399
400    /**
401     * The command to trigger a WiFi reset should trigger a wifi reset in WifiStateMachine through
402     * the WifiStateMachine.setSupplicantRunning(false) call when in STA mode.
403     * WiFi is in connect mode, calls to reset the wifi stack due to connection failures
404     * should trigger a supplicant stop, and subsequently, a driver reload.
405     * Create and start WifiController in DeviceActiveState, send command to restart WiFi
406     * <p>
407     * Expected: WiFiController should call WifiStateMachine.setSupplicantRunning(false),
408     * WifiStateMachine should enter CONNECT_MODE and the wifi driver should be started.
409     */
410    @Test
411    public void testRestartWifiStackInStaEnabledState() throws Exception {
412        enableWifi();
413
414        reset(mWifiStateMachine);
415        assertEquals("DeviceActiveState", getCurrentState().getName());
416        mWifiController.sendMessage(CMD_RESTART_WIFI);
417        mLooper.dispatchAll();
418        InOrder inOrder = inOrder(mWifiStateMachine);
419        inOrder.verify(mWifiStateMachine).setSupplicantRunning(false);
420        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
421        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
422        assertEquals("DeviceActiveState", getCurrentState().getName());
423    }
424
425    /**
426     * The command to trigger a WiFi reset should not trigger a reset when in ECM mode.
427     * Enable wifi and enter ECM state, send command to restart wifi.
428     * <p>
429     * Expected: The command to trigger a wifi reset should be ignored and we should remain in ECM
430     * mode.
431     */
432    @Test
433    public void testRestartWifiStackDoesNotExitECMMode() throws Exception {
434        enableWifi();
435        assertEquals("DeviceActiveState", getCurrentState().getName());
436        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
437
438        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
439        mLooper.dispatchAll();
440        assertInEcm(true);
441
442        reset(mWifiStateMachine);
443        mWifiController.sendMessage(CMD_RESTART_WIFI);
444        mLooper.dispatchAll();
445        assertInEcm(true);
446        verifyZeroInteractions(mWifiStateMachine);
447    }
448
449    /**
450     * The command to trigger a WiFi reset should not trigger a reset when in AP mode.
451     * Enter AP mode, send command to restart wifi.
452     * <p>
453     * Expected: The command to trigger a wifi reset should be ignored and we should remain in AP
454     * mode.
455     */
456    @Test
457    public void testRestartWifiStackDoesNotExitAPMode() throws Exception {
458        mWifiController.obtainMessage(CMD_SET_AP, 1).sendToTarget();
459        mLooper.dispatchAll();
460        assertEquals("ApEnabledState", getCurrentState().getName());
461
462        reset(mWifiStateMachine);
463        mWifiController.sendMessage(CMD_RESTART_WIFI);
464        mLooper.dispatchAll();
465        verifyZeroInteractions(mWifiStateMachine);
466    }
467}
468