WifiStateMachineTest.java revision 94bd575cb4766ed0dfbaad0fc7719a9e9e85a260
1/*
2 * Copyright (C) 2015 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.junit.Assert.assertTrue;
21import static org.mockito.Mockito.any;
22import static org.mockito.Mockito.anyBoolean;
23import static org.mockito.Mockito.anyInt;
24import static org.mockito.Mockito.anyObject;
25import static org.mockito.Mockito.anyString;
26import static org.mockito.Mockito.mock;
27import static org.mockito.Mockito.verify;
28import static org.mockito.Mockito.when;
29import static org.mockito.Mockito.withSettings;
30
31import android.content.ContentResolver;
32import android.content.Context;
33import android.content.pm.PackageManager;
34import android.content.res.Resources;
35import android.net.BaseDhcpStateMachine;
36import android.net.ConnectivityManager;
37import android.net.DhcpResults;
38import android.net.DhcpStateMachine;
39import android.net.ip.IpReachabilityMonitor;
40import android.net.wifi.ScanResult;
41import android.net.wifi.SupplicantState;
42import android.net.wifi.WifiConfiguration;
43import android.net.wifi.WifiManager;
44import android.net.wifi.WifiSsid;
45import android.net.wifi.p2p.IWifiP2pManager;
46import android.os.BatteryStats;
47import android.os.Handler;
48import android.os.HandlerThread;
49import android.os.IBinder;
50import android.os.IInterface;
51import android.os.INetworkManagementService;
52import android.os.IPowerManager;
53import android.os.Looper;
54import android.os.Message;
55import android.os.Messenger;
56import android.os.PowerManager;
57import android.provider.Settings;
58import android.test.suitebuilder.annotation.SmallTest;
59import android.util.Log;
60
61import com.android.internal.R;
62import com.android.internal.app.IBatteryStats;
63import com.android.internal.util.AsyncChannel;
64import com.android.internal.util.IState;
65import com.android.internal.util.StateMachine;
66import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
67import com.android.server.wifi.hotspot2.NetworkDetail;
68import com.android.server.wifi.hotspot2.SupplicantBridge;
69import com.android.server.wifi.hotspot2.omadm.MOManager;
70import com.android.server.wifi.hotspot2.osu.OSUManager;
71import com.android.server.wifi.p2p.WifiP2pServiceImpl;
72
73import org.junit.After;
74import org.junit.Before;
75import org.junit.Test;
76import org.mockito.Mock;
77import org.mockito.Mockito;
78import org.mockito.MockitoAnnotations;
79
80import java.io.ByteArrayOutputStream;
81import java.io.PrintWriter;
82import java.lang.reflect.Field;
83import java.lang.reflect.InvocationTargetException;
84import java.lang.reflect.Method;
85import java.util.ArrayList;
86import java.util.HashMap;
87import java.util.List;
88import java.util.Map;
89
90/**
91 * Unit tests for {@link com.android.server.wifi.WifiStateMachine}.
92 */
93@SmallTest
94public class WifiStateMachineTest {
95    public static final String TAG = "WifiStateMachineTest";
96
97    private static <T> T mockWithInterfaces(Class<T> class1, Class<?>... interfaces) {
98        return mock(class1, withSettings().extraInterfaces(interfaces));
99    }
100
101    private static <T, I> IBinder mockService(Class<T> class1, Class<I> iface) {
102        T tImpl = mockWithInterfaces(class1, iface);
103        IBinder binder = mock(IBinder.class);
104        when(((IInterface) tImpl).asBinder()).thenReturn(binder);
105        when(binder.queryLocalInterface(iface.getCanonicalName()))
106                .thenReturn((IInterface) tImpl);
107        return binder;
108    }
109
110    private void enableDebugLogs() {
111        mWsm.enableVerboseLogging(1);
112    }
113
114    private static void installWlanWifiNative(WifiNative wifiNative) throws Exception {
115        Field field = WifiNative.class.getDeclaredField("wlanNativeInterface");
116        field.setAccessible(true);
117        field.set(null, wifiNative);
118
119        when(wifiNative.getInterfaceName()).thenReturn("wlan0");
120    }
121
122    private FrameworkFacade getFrameworkFacade() throws InterruptedException {
123        FrameworkFacade facade = mock(FrameworkFacade.class);
124
125        when(facade.makeBaseLogger()).thenReturn(mock(BaseWifiLogger.class));
126        when(facade.getService(Context.NETWORKMANAGEMENT_SERVICE)).thenReturn(
127                mockWithInterfaces(IBinder.class, INetworkManagementService.class));
128
129        IBinder p2pBinder = mockService(WifiP2pServiceImpl.class, IWifiP2pManager.class);
130        when(facade.getService(Context.WIFI_P2P_SERVICE)).thenReturn(p2pBinder);
131
132        WifiP2pServiceImpl p2pm = (WifiP2pServiceImpl) p2pBinder.queryLocalInterface(
133                IWifiP2pManager.class.getCanonicalName());
134
135        final Object sync = new Object();
136        synchronized (sync) {
137            mP2pThread = new HandlerThread("WifiP2pMockThread") {
138                @Override
139                protected void onLooperPrepared() {
140                    synchronized (sync) {
141                        sync.notifyAll();
142                    }
143                }
144            };
145
146            mP2pThread.start();
147            sync.wait();
148        }
149
150        Handler handler = new Handler(mP2pThread.getLooper());
151        when(p2pm.getP2pStateMachineMessenger()).thenReturn(new Messenger(handler));
152
153        IBinder batteryStatsBinder = mockService(BatteryStats.class, IBatteryStats.class);
154        when(facade.getService(BatteryStats.SERVICE_NAME)).thenReturn(batteryStatsBinder);
155
156        when(facade.makeOsuManager(any(WifiConfigStore.class), any(Context.class), any(
157                SupplicantBridge.class), any(MOManager.class), any(WifiStateMachine.class)))
158                .thenReturn(mock(OSUManager.class));
159
160        when(facade.makeDhcpStateMachine(
161                any(Context.class), any(StateMachine.class), any(String.class))).thenReturn(
162                mock(BaseDhcpStateMachine.class));
163
164        when(facade.makeIpReachabilityMonitor(any(Context.class), anyString(),
165                any(IpReachabilityMonitor.Callback.class))).thenReturn(
166                mock(IpReachabilityMonitor.class));
167
168        return facade;
169    }
170
171    private Context getContext() throws Exception {
172        PackageManager pkgMgr = mock(PackageManager.class);
173        when(pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
174
175        Context context = mock(Context.class);
176        when(context.getPackageManager()).thenReturn(pkgMgr);
177        when(context.getContentResolver()).thenReturn(mock(ContentResolver.class));
178
179        MockResources resources = new com.android.server.wifi.MockResources();
180        when(context.getResources()).thenReturn(resources);
181
182        ContentResolver cr = mock(ContentResolver.class);
183        when(context.getContentResolver()).thenReturn(cr);
184
185        when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(
186                new PowerManager(context, mock(IPowerManager.class), new Handler()));
187
188        mAlarmManager = new MockAlarmManager();
189        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
190                mAlarmManager.getAlarmManager());
191
192        when(context.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(
193                mock(ConnectivityManager.class));
194
195        return context;
196    }
197
198    private Resources getMockResources() {
199        MockResources resources = new MockResources();
200        resources.setBoolean(R.bool.config_wifi_enable_wifi_firmware_debugging, false);
201        return resources;
202    }
203
204    private IState getCurrentState() throws
205            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
206        Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
207        method.setAccessible(true);
208        return (IState) method.invoke(mWsm);
209    }
210
211    private static HandlerThread getWsmHandlerThread(WifiStateMachine wsm) throws
212            NoSuchFieldException, InvocationTargetException, IllegalAccessException {
213        Field field = StateMachine.class.getDeclaredField("mSmThread");
214        field.setAccessible(true);
215        return (HandlerThread) field.get(wsm);
216    }
217
218    private static void stopLooper(final Looper looper) throws Exception {
219        new Handler(looper).post(new Runnable() {
220            @Override
221            public void run() {
222                looper.quitSafely();
223            }
224        });
225    }
226
227    private void wait(int delayInMs) throws InterruptedException {
228        Looper looper = mWsmThread.getLooper();
229        final Handler handler = new Handler(looper);
230        synchronized (handler) {
231            handler.postDelayed(new Runnable() {
232                @Override
233                public void run() {
234                    synchronized (handler) {
235                        handler.notifyAll();
236                    }
237                }
238            }, delayInMs);
239
240            handler.wait();
241        }
242    }
243
244    private void dumpState() {
245        ByteArrayOutputStream stream = new ByteArrayOutputStream();
246        PrintWriter writer = new PrintWriter(stream);
247        mWsm.dump(null, writer, null);
248        writer.flush();
249        Log.d(TAG, "WifiStateMachine state -" + stream.toString());
250    }
251
252    private static ScanDetail getGoogleGuestScanDetail(int rssi) {
253        ScanResult.InformationElement ie[] = new ScanResult.InformationElement[1];
254        ie[0] = ScanResults.generateSsidIe(sSSID);
255        NetworkDetail nd = new NetworkDetail(sBSSID, ie, new ArrayList<String>(), sFreq);
256        ScanDetail detail = new ScanDetail(nd, sWifiSsid, sBSSID, "", rssi, sFreq,
257                Long.MAX_VALUE /* needed so that scan results aren't rejected because
258                                  there older than scan start */);
259        return detail;
260    }
261
262    private ArrayList<ScanDetail> getMockScanResults() {
263        ScanResults sr = ScanResults.create(0, 2412, 2437, 2462, 5180, 5220, 5745, 5825);
264        ArrayList<ScanDetail> list = sr.getScanDetailArrayList();
265
266        int rssi = -65;
267        list.add(getGoogleGuestScanDetail(rssi));
268        return list;
269    }
270
271    static final String   sSSID = "\"GoogleGuest\"";
272    static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
273    static final String   sHexSSID = sWifiSsid.getHexString().replace("0x", "").replace("22", "");
274    static final String   sBSSID = "01:02:03:04:05:06";
275    static final int      sFreq = 2437;
276
277    WifiStateMachine mWsm;
278    HandlerThread mWsmThread;
279    HandlerThread mP2pThread;
280    HandlerThread mSyncThread;
281    AsyncChannel  mWsmAsyncChannel;
282    MockAlarmManager mAlarmManager;
283    MockWifiMonitor mWifiMonitor;
284
285    @Mock WifiNative mWifiNative;
286    @Mock SupplicantStateTracker mSupplicantStateTracker;
287
288    public WifiStateMachineTest() throws Exception {
289    }
290
291    @Before
292    public void setUp() throws Exception {
293        Log.d(TAG, "Setting up ...");
294
295        // Ensure looper exists
296        MockLooper looper = new MockLooper();
297
298        MockitoAnnotations.initMocks(this);
299
300        /** uncomment this to enable logs from WifiStateMachines */
301        // enableDebugLogs();
302
303        installWlanWifiNative(mWifiNative);
304        mWifiMonitor = new MockWifiMonitor();
305
306        FrameworkFacade factory = getFrameworkFacade();
307        Context context = getContext();
308
309        Resources resources = getMockResources();
310        when(context.getResources()).thenReturn(resources);
311
312        when(factory.getIntegerSetting(context,
313                Settings.Global.WIFI_FREQUENCY_BAND,
314                WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn(
315                WifiManager.WIFI_FREQUENCY_BAND_AUTO);
316
317        when(factory.makeApConfigStore(Mockito.eq(context), any(Handler.class)))
318                .thenCallRealMethod();
319
320        when(factory.makeSupplicantStateTracker(
321                any(Context.class), any(WifiStateMachine.class), any(WifiConfigStore.class),
322                any(Handler.class))).thenReturn(mSupplicantStateTracker);
323
324        mWsm = new WifiStateMachine(context, null, factory);
325        mWsmThread = getWsmHandlerThread(mWsm);
326
327        final Object sync = new Object();
328        synchronized (sync) {
329            mSyncThread = new HandlerThread("SynchronizationThread");
330            final AsyncChannel channel = new AsyncChannel();
331            mSyncThread.start();
332            Handler handler = new Handler(mSyncThread.getLooper()) {
333                @Override
334                public void handleMessage(Message msg) {
335                    switch (msg.what) {
336                        case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
337                            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
338                                mWsmAsyncChannel = channel;
339                                synchronized (sync) {
340                                    sync.notifyAll();
341                                    Log.d(TAG, "Successfully connected " + this);
342                                }
343                            } else {
344                                Log.d(TAG, "Failed to connect Command channel " + this);
345                            }
346                            break;
347                        case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
348                            Log.d(TAG, "Command channel disconnected" + this);
349                            break;
350                    }
351                }
352            };
353
354            channel.connect(context, handler, mWsm.getMessenger());
355            sync.wait();
356        }
357
358        /* Now channel is supposed to be connected */
359    }
360
361    @After
362    public void cleanUp() throws Exception {
363
364        if (mSyncThread != null) stopLooper(mSyncThread.getLooper());
365        if (mWsmThread != null) stopLooper(mWsmThread.getLooper());
366        if (mP2pThread != null) stopLooper(mP2pThread.getLooper());
367
368        mWsmThread = null;
369        mP2pThread = null;
370        mSyncThread = null;
371        mWsmAsyncChannel = null;
372        mWsm = null;
373    }
374
375    @Test
376    public void createNew() throws Exception {
377        assertEquals("InitialState", getCurrentState().getName());
378
379        mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
380        wait(200);
381        assertEquals("InitialState", getCurrentState().getName());
382    }
383
384    @Test
385    public void loadComponents() throws Exception {
386
387        when(mWifiNative.loadDriver()).thenReturn(true);
388        when(mWifiNative.startHal()).thenReturn(true);
389        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
390
391        mWsm.setSupplicantRunning(true);
392        wait(200);
393        assertEquals("SupplicantStartingState", getCurrentState().getName());
394
395        when(mWifiNative.setBand(anyInt())).thenReturn(true);
396        when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
397        when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
398        when(mWifiNative.setModelName(anyString())).thenReturn(true);
399        when(mWifiNative.setModelNumber(anyString())).thenReturn(true);
400        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
401        when(mWifiNative.setConfigMethods(anyString())).thenReturn(true);
402        when(mWifiNative.setDeviceType(anyString())).thenReturn(true);
403        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
404        when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true);
405        when(mWifiNative.enableBackgroundScan(anyBoolean())).thenReturn(true);
406
407        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
408        wait(200);
409        assertEquals("DisconnectedState", getCurrentState().getName());
410    }
411
412    @Test
413    public void loadComponentsFailure() throws Exception {
414        when(mWifiNative.loadDriver()).thenReturn(false);
415        when(mWifiNative.startHal()).thenReturn(false);
416        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(false);
417
418        mWsm.setSupplicantRunning(true);
419        wait(200);
420        assertEquals("InitialState", getCurrentState().getName());
421
422        when(mWifiNative.loadDriver()).thenReturn(true);
423        mWsm.setSupplicantRunning(true);
424        wait(200);
425        assertEquals("InitialState", getCurrentState().getName());
426
427        when(mWifiNative.startHal()).thenReturn(true);
428        mWsm.setSupplicantRunning(true);
429        wait(200);
430        assertEquals("InitialState", getCurrentState().getName());
431    }
432
433    @Test
434    public void addNetwork() throws Exception {
435
436        loadComponents();
437
438        final HashMap<String, String> nameToValue = new HashMap<String, String>();
439
440        when(mWifiNative.addNetwork()).thenReturn(0);
441        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString()))
442                .then(new AnswerWithArguments<Boolean>() {
443                    public boolean answer(int netId, String name, String value) {
444                        if (netId != 0) {
445                            Log.d(TAG, "Can't set var " + name + " for " + netId);
446                            return false;
447                        }
448
449                        Log.d(TAG, "Setting var " + name + " to " + value + " for " + netId);
450                        nameToValue.put(name, value);
451                        return true;
452                    }
453                });
454
455        when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject()))
456                .then(new AnswerWithArguments<Boolean>() {
457                        public boolean answer(int netId, String name, Map<String, String> values) {
458                            if (netId != 0) {
459                                Log.d(TAG, "Can't set extra " + name + " for " + netId);
460                                return false;
461                            }
462
463                            Log.d(TAG, "Setting extra for " + netId);
464                            return true;
465                        }
466                    });
467
468        when(mWifiNative.getNetworkVariable(anyInt(), anyString()))
469                .then(new AnswerWithArguments<String>() {
470                    public String answer(int netId, String name) throws Throwable {
471                        if (netId != 0) {
472                            Log.d(TAG, "Can't find var " + name + " for " + netId);
473                            return null;
474                        }
475                        String value = nameToValue.get(name);
476                        if (value != null) {
477                            Log.d(TAG, "Returning var " + name + " to " + value + " for " + netId);
478                        } else {
479                            Log.d(TAG, "Can't find var " + name + " for " + netId);
480                        }
481                        return value;
482                    }
483                });
484
485        WifiConfiguration config = new WifiConfiguration();
486        config.SSID = sSSID;
487        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
488        mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config);
489        wait(200);
490
491        verify(mWifiNative).addNetwork();
492        verify(mWifiNative).setNetworkVariable(0, "ssid", sHexSSID);
493
494        List<WifiConfiguration> configs = mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel);
495        assertEquals(1, configs.size());
496
497        WifiConfiguration config2 = configs.get(0);
498        assertEquals("\"GoogleGuest\"", config2.SSID);
499        assertTrue(config2.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
500    }
501
502    @Test
503    public void scan() throws Exception {
504
505        addNetwork();
506
507        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
508
509        mWsm.startScan(-1, 0, null, null);
510        wait(200);
511
512        verify(mWifiNative).scan(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, null);
513
514        when(mWifiNative.getScanResults()).thenReturn(getMockScanResults());
515        mWsm.sendMessage(WifiMonitor.SCAN_RESULTS_EVENT);
516
517        wait(200);
518        List<ScanResult> results = mWsm.syncGetScanResultsList();
519        assertEquals(8, results.size());
520    }
521
522    @Test
523    public void connect() throws Exception {
524
525        addNetwork();
526
527        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
528        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
529        wait(200);
530
531        verify(mWifiNative).enableNetwork(0, true);
532
533        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
534        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
535                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
536        wait(200);
537
538        assertEquals("ObtainingIpState", getCurrentState().getName());
539
540        DhcpResults dhcpResults = new DhcpResults();
541        dhcpResults.setGateway("1.2.3.4");
542        dhcpResults.setIpAddress("192.168.1.100", 0);
543        dhcpResults.addDns("8.8.8.8");
544        dhcpResults.setLeaseDuration(3600);
545
546        mWsm.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, DhcpStateMachine.DHCP_SUCCESS, 0,
547                dhcpResults);
548        wait(200);
549
550        assertEquals("ConnectedState", getCurrentState().getName());
551    }
552
553    @Test
554    public void disconnect() throws Exception {
555        connect();
556
557        mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, "01:02:03:04:05:06");
558        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
559                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
560        wait(200);
561
562        assertEquals("DisconnectedState", getCurrentState().getName());
563    }
564}
565