WifiStateMachineTest.java revision c79666b79e273ceaa2f74090b02ca6cf83c61387
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    @Mock WifiMetrics mWifiMetrics;
288
289    public WifiStateMachineTest() throws Exception {
290    }
291
292    @Before
293    public void setUp() throws Exception {
294        Log.d(TAG, "Setting up ...");
295
296        // Ensure looper exists
297        MockLooper looper = new MockLooper();
298
299        MockitoAnnotations.initMocks(this);
300
301        /** uncomment this to enable logs from WifiStateMachines */
302        // enableDebugLogs();
303
304        installWlanWifiNative(mWifiNative);
305        mWifiMonitor = new MockWifiMonitor();
306        mWifiMetrics = mock(WifiMetrics.class);
307        FrameworkFacade factory = getFrameworkFacade();
308        Context context = getContext();
309
310        Resources resources = getMockResources();
311        when(context.getResources()).thenReturn(resources);
312
313        when(factory.getIntegerSetting(context,
314                Settings.Global.WIFI_FREQUENCY_BAND,
315                WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn(
316                WifiManager.WIFI_FREQUENCY_BAND_AUTO);
317
318        when(factory.makeApConfigStore(Mockito.eq(context)))
319                .thenCallRealMethod();
320
321        when(factory.makeSupplicantStateTracker(
322                any(Context.class), any(WifiStateMachine.class), any(WifiConfigStore.class),
323                any(Handler.class))).thenReturn(mSupplicantStateTracker);
324
325        mWsm = new WifiStateMachine(context, null, factory, mWifiMetrics);
326        mWsmThread = getWsmHandlerThread(mWsm);
327
328        final Object sync = new Object();
329        synchronized (sync) {
330            mSyncThread = new HandlerThread("SynchronizationThread");
331            final AsyncChannel channel = new AsyncChannel();
332            mSyncThread.start();
333            Handler handler = new Handler(mSyncThread.getLooper()) {
334                @Override
335                public void handleMessage(Message msg) {
336                    switch (msg.what) {
337                        case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
338                            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
339                                mWsmAsyncChannel = channel;
340                                synchronized (sync) {
341                                    sync.notifyAll();
342                                    Log.d(TAG, "Successfully connected " + this);
343                                }
344                            } else {
345                                Log.d(TAG, "Failed to connect Command channel " + this);
346                            }
347                            break;
348                        case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
349                            Log.d(TAG, "Command channel disconnected" + this);
350                            break;
351                    }
352                }
353            };
354
355            channel.connect(context, handler, mWsm.getMessenger());
356            sync.wait();
357        }
358
359        /* Now channel is supposed to be connected */
360    }
361
362    @After
363    public void cleanUp() throws Exception {
364
365        if (mSyncThread != null) stopLooper(mSyncThread.getLooper());
366        if (mWsmThread != null) stopLooper(mWsmThread.getLooper());
367        if (mP2pThread != null) stopLooper(mP2pThread.getLooper());
368
369        mWsmThread = null;
370        mP2pThread = null;
371        mSyncThread = null;
372        mWsmAsyncChannel = null;
373        mWsm = null;
374    }
375
376    @Test
377    public void createNew() throws Exception {
378        assertEquals("InitialState", getCurrentState().getName());
379
380        mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
381        wait(200);
382        assertEquals("InitialState", getCurrentState().getName());
383    }
384
385    @Test
386    public void loadComponents() throws Exception {
387
388        when(mWifiNative.loadDriver()).thenReturn(true);
389        when(mWifiNative.startHal()).thenReturn(true);
390        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
391
392        mWsm.setSupplicantRunning(true);
393        wait(200);
394        assertEquals("SupplicantStartingState", getCurrentState().getName());
395
396        when(mWifiNative.setBand(anyInt())).thenReturn(true);
397        when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
398        when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
399        when(mWifiNative.setModelName(anyString())).thenReturn(true);
400        when(mWifiNative.setModelNumber(anyString())).thenReturn(true);
401        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
402        when(mWifiNative.setConfigMethods(anyString())).thenReturn(true);
403        when(mWifiNative.setDeviceType(anyString())).thenReturn(true);
404        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
405        when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true);
406        when(mWifiNative.enableBackgroundScan(anyBoolean())).thenReturn(true);
407
408        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
409        wait(200);
410        assertEquals("DisconnectedState", getCurrentState().getName());
411    }
412
413    @Test
414    public void loadComponentsFailure() throws Exception {
415        when(mWifiNative.loadDriver()).thenReturn(false);
416        when(mWifiNative.startHal()).thenReturn(false);
417        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(false);
418
419        mWsm.setSupplicantRunning(true);
420        wait(200);
421        assertEquals("InitialState", getCurrentState().getName());
422
423        when(mWifiNative.loadDriver()).thenReturn(true);
424        mWsm.setSupplicantRunning(true);
425        wait(200);
426        assertEquals("InitialState", getCurrentState().getName());
427
428        when(mWifiNative.startHal()).thenReturn(true);
429        mWsm.setSupplicantRunning(true);
430        wait(200);
431        assertEquals("InitialState", getCurrentState().getName());
432    }
433
434    @Test
435    public void addNetwork() throws Exception {
436
437        loadComponents();
438
439        final HashMap<String, String> nameToValue = new HashMap<String, String>();
440
441        when(mWifiNative.addNetwork()).thenReturn(0);
442        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString()))
443                .then(new AnswerWithArguments<Boolean>() {
444                    public boolean answer(int netId, String name, String value) {
445                        if (netId != 0) {
446                            Log.d(TAG, "Can't set var " + name + " for " + netId);
447                            return false;
448                        }
449
450                        Log.d(TAG, "Setting var " + name + " to " + value + " for " + netId);
451                        nameToValue.put(name, value);
452                        return true;
453                    }
454                });
455
456        when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject()))
457                .then(new AnswerWithArguments<Boolean>() {
458                        public boolean answer(int netId, String name, Map<String, String> values) {
459                            if (netId != 0) {
460                                Log.d(TAG, "Can't set extra " + name + " for " + netId);
461                                return false;
462                            }
463
464                            Log.d(TAG, "Setting extra for " + netId);
465                            return true;
466                        }
467                    });
468
469        when(mWifiNative.getNetworkVariable(anyInt(), anyString()))
470                .then(new AnswerWithArguments<String>() {
471                    public String answer(int netId, String name) throws Throwable {
472                        if (netId != 0) {
473                            Log.d(TAG, "Can't find var " + name + " for " + netId);
474                            return null;
475                        }
476                        String value = nameToValue.get(name);
477                        if (value != null) {
478                            Log.d(TAG, "Returning var " + name + " to " + value + " for " + netId);
479                        } else {
480                            Log.d(TAG, "Can't find var " + name + " for " + netId);
481                        }
482                        return value;
483                    }
484                });
485
486        WifiConfiguration config = new WifiConfiguration();
487        config.SSID = sSSID;
488        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
489        mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config);
490        wait(200);
491
492        verify(mWifiNative).addNetwork();
493        verify(mWifiNative).setNetworkVariable(0, "ssid", sHexSSID);
494
495        List<WifiConfiguration> configs = mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel);
496        assertEquals(1, configs.size());
497
498        WifiConfiguration config2 = configs.get(0);
499        assertEquals("\"GoogleGuest\"", config2.SSID);
500        assertTrue(config2.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
501    }
502
503    @Test
504    public void scan() throws Exception {
505
506        addNetwork();
507
508        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
509
510        mWsm.startScan(-1, 0, null, null);
511        wait(200);
512
513        verify(mWifiNative).scan(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, null);
514
515        when(mWifiNative.getScanResults()).thenReturn(getMockScanResults());
516        mWsm.sendMessage(WifiMonitor.SCAN_RESULTS_EVENT);
517
518        wait(200);
519        List<ScanResult> results = mWsm.syncGetScanResultsList();
520        assertEquals(8, results.size());
521    }
522
523    @Test
524    public void connect() throws Exception {
525
526        addNetwork();
527
528        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
529        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
530        wait(200);
531
532        verify(mWifiNative).enableNetwork(0, true);
533
534        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
535        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
536                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
537        wait(200);
538
539        assertEquals("ObtainingIpState", getCurrentState().getName());
540
541        DhcpResults dhcpResults = new DhcpResults();
542        dhcpResults.setGateway("1.2.3.4");
543        dhcpResults.setIpAddress("192.168.1.100", 0);
544        dhcpResults.addDns("8.8.8.8");
545        dhcpResults.setLeaseDuration(3600);
546
547        mWsm.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, DhcpStateMachine.DHCP_SUCCESS, 0,
548                dhcpResults);
549        wait(200);
550
551        assertEquals("ConnectedState", getCurrentState().getName());
552    }
553
554    @Test
555    public void disconnect() throws Exception {
556        connect();
557
558        mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, "01:02:03:04:05:06");
559        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
560                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED));
561        wait(200);
562
563        assertEquals("DisconnectedState", getCurrentState().getName());
564    }
565}
566