| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.server.wifi; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.anyBoolean; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.anyObject; |
| import static org.mockito.Mockito.anyString; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| import static org.mockito.Mockito.withSettings; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.net.BaseDhcpStateMachine; |
| import android.net.ConnectivityManager; |
| import android.net.DhcpResults; |
| import android.net.DhcpStateMachine; |
| import android.net.ip.IpReachabilityMonitor; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.SupplicantState; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiSsid; |
| import android.net.wifi.p2p.IWifiP2pManager; |
| import android.os.BatteryStats; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.IInterface; |
| import android.os.INetworkManagementService; |
| import android.os.IPowerManager; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.PowerManager; |
| import android.provider.Settings; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.util.Log; |
| |
| import com.android.internal.R; |
| import com.android.internal.app.IBatteryStats; |
| import com.android.internal.util.AsyncChannel; |
| import com.android.internal.util.IState; |
| import com.android.internal.util.StateMachine; |
| import com.android.server.wifi.hotspot2.NetworkDetail; |
| import com.android.server.wifi.hotspot2.SupplicantBridge; |
| import com.android.server.wifi.hotspot2.omadm.MOManager; |
| import com.android.server.wifi.hotspot2.osu.OSUManager; |
| import com.android.server.wifi.p2p.WifiP2pServiceImpl; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.PrintWriter; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Example Unit Test File |
| */ |
| @SmallTest |
| public class WifiStateMachineTest { |
| public static final String TAG = "WifiStateMachineTest"; |
| |
| private static <T> T mockWithInterfaces(Class<T> class1, Class<?>... interfaces) { |
| return mock(class1, withSettings().extraInterfaces(interfaces)); |
| } |
| |
| private static <T, I> IBinder mockService(Class<T> class1, Class<I> iface) { |
| T tImpl = mockWithInterfaces(class1, iface); |
| IBinder binder = mock(IBinder.class); |
| when(((IInterface) tImpl).asBinder()).thenReturn(binder); |
| when(binder.queryLocalInterface(iface.getCanonicalName())) |
| .thenReturn((IInterface) tImpl); |
| return binder; |
| } |
| |
| private void enableDebugLogs() { |
| mWsm.enableVerboseLogging(1); |
| } |
| |
| private static void installWlanWifiNative(WifiNative wifiNative) throws Exception { |
| Field field = WifiNative.class.getDeclaredField("wlanNativeInterface"); |
| field.setAccessible(true); |
| field.set(null, wifiNative); |
| |
| when(wifiNative.getInterfaceName()).thenReturn("wlan0"); |
| } |
| |
| private FrameworkFacade getFrameworkFacade() throws InterruptedException { |
| FrameworkFacade facade = mock(FrameworkFacade.class); |
| |
| when(facade.makeBaseLogger()).thenReturn(mock(BaseWifiLogger.class)); |
| when(facade.getService(Context.NETWORKMANAGEMENT_SERVICE)).thenReturn( |
| mockWithInterfaces(IBinder.class, INetworkManagementService.class)); |
| |
| IBinder p2pBinder = mockService(WifiP2pServiceImpl.class, IWifiP2pManager.class); |
| when(facade.getService(Context.WIFI_P2P_SERVICE)).thenReturn(p2pBinder); |
| |
| WifiP2pServiceImpl p2pm = (WifiP2pServiceImpl) p2pBinder.queryLocalInterface( |
| IWifiP2pManager.class.getCanonicalName()); |
| |
| final Object sync = new Object(); |
| synchronized (sync) { |
| mP2pThread = new HandlerThread("WifiP2pMockThread") { |
| @Override |
| protected void onLooperPrepared() { |
| synchronized (sync) { |
| sync.notifyAll(); |
| } |
| } |
| }; |
| |
| mP2pThread.start(); |
| sync.wait(); |
| } |
| |
| Handler handler = new Handler(mP2pThread.getLooper()); |
| when(p2pm.getP2pStateMachineMessenger()).thenReturn(new Messenger(handler)); |
| |
| IBinder batteryStatsBinder = mockService(BatteryStats.class, IBatteryStats.class); |
| when(facade.getService(BatteryStats.SERVICE_NAME)).thenReturn(batteryStatsBinder); |
| |
| when(facade.makeOsuManager(any(WifiConfigStore.class), any(Context.class), any( |
| SupplicantBridge.class), any(MOManager.class), any(WifiStateMachine.class))) |
| .thenReturn(mock(OSUManager.class)); |
| |
| when(facade.makeDhcpStateMachine( |
| any(Context.class), any(StateMachine.class), any(String.class))).thenReturn( |
| mock(BaseDhcpStateMachine.class)); |
| |
| when(facade.makeIpReachabilityMonitor(any(Context.class), anyString(), |
| any(IpReachabilityMonitor.Callback.class))).thenReturn( |
| mock(IpReachabilityMonitor.class)); |
| |
| return facade; |
| } |
| |
| private Context getContext() throws Exception { |
| PackageManager pkgMgr = mock(PackageManager.class); |
| when(pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true); |
| |
| Context context = mock(Context.class); |
| when(context.getPackageManager()).thenReturn(pkgMgr); |
| when(context.getContentResolver()).thenReturn(mock(ContentResolver.class)); |
| |
| MockResources resources = new com.android.server.wifi.MockResources(); |
| when(context.getResources()).thenReturn(resources); |
| |
| ContentResolver cr = mock(ContentResolver.class); |
| when(context.getContentResolver()).thenReturn(cr); |
| |
| when(context.getSystemService(Context.POWER_SERVICE)).thenReturn( |
| new PowerManager(context, mock(IPowerManager.class), new Handler())); |
| |
| mAlarmManager = new MockAlarmManager(); |
| when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn( |
| mAlarmManager.getAlarmManager()); |
| |
| when(context.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn( |
| mock(ConnectivityManager.class)); |
| |
| return context; |
| } |
| |
| private Resources getMockResources() { |
| MockResources resources = new MockResources(); |
| resources.setBoolean(R.bool.config_wifi_enable_wifi_firmware_debugging, false); |
| return resources; |
| } |
| |
| private IState getCurrentState() throws |
| NoSuchMethodException, InvocationTargetException, IllegalAccessException { |
| Method method = StateMachine.class.getDeclaredMethod("getCurrentState"); |
| method.setAccessible(true); |
| return (IState) method.invoke(mWsm); |
| } |
| |
| private static HandlerThread getWsmHandlerThread(WifiStateMachine wsm) throws |
| NoSuchFieldException, InvocationTargetException, IllegalAccessException { |
| Field field = StateMachine.class.getDeclaredField("mSmThread"); |
| field.setAccessible(true); |
| return (HandlerThread) field.get(wsm); |
| } |
| |
| private static void stopLooper(final Looper looper) throws Exception { |
| new Handler(looper).post(new Runnable() { |
| @Override |
| public void run() { |
| looper.quitSafely(); |
| } |
| }); |
| } |
| |
| private void wait(int delayInMs) throws InterruptedException { |
| Looper looper = mWsmThread.getLooper(); |
| final Handler handler = new Handler(looper); |
| synchronized (handler) { |
| handler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| synchronized (handler) { |
| handler.notifyAll(); |
| } |
| } |
| }, delayInMs); |
| |
| handler.wait(); |
| } |
| } |
| |
| private void dumpState() { |
| ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
| PrintWriter writer = new PrintWriter(stream); |
| mWsm.dump(null, writer, null); |
| writer.flush(); |
| Log.d(TAG, "WifiStateMachine state -" + stream.toString()); |
| } |
| |
| private static ScanDetail getGoogleGuestScanDetail(int rssi) { |
| ScanResult.InformationElement ie[] = new ScanResult.InformationElement[1]; |
| ie[0] = ScanResults.generateSsidIe(sSSID); |
| NetworkDetail nd = new NetworkDetail(sBSSID, ie, new ArrayList<String>(), sFreq); |
| ScanDetail detail = new ScanDetail(nd, sWifiSsid, sBSSID, "", rssi, sFreq, |
| Long.MAX_VALUE /* needed so that scan results aren't rejected because |
| there older than scan start */); |
| return detail; |
| } |
| |
| private ArrayList<ScanDetail> getMockScanResults() { |
| ScanResults sr = ScanResults.create(0, 2412, 2437, 2462, 5180, 5220, 5745, 5825); |
| ArrayList<ScanDetail> list = sr.getScanDetailArrayList(); |
| |
| int rssi = -65; |
| list.add(getGoogleGuestScanDetail(rssi)); |
| return list; |
| } |
| |
| static final String sSSID = "\"GoogleGuest\""; |
| static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID); |
| static final String sHexSSID = sWifiSsid.getHexString().replace("0x", "").replace("22", ""); |
| static final String sBSSID = "01:02:03:04:05:06"; |
| static final int sFreq = 2437; |
| |
| WifiStateMachine mWsm; |
| HandlerThread mWsmThread; |
| HandlerThread mP2pThread; |
| HandlerThread mSyncThread; |
| AsyncChannel mWsmAsyncChannel; |
| MockAlarmManager mAlarmManager; |
| MockWifiMonitor mWifiMonitor; |
| |
| @Mock WifiNative mWifiNative; |
| @Mock SupplicantStateTracker mSupplicantStateTracker; |
| |
| public WifiStateMachineTest() throws Exception { |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| Log.d(TAG, "Setting up ..."); |
| |
| // Ensure looper exists |
| MockLooper looper = new MockLooper(); |
| |
| MockitoAnnotations.initMocks(this); |
| |
| /** uncomment this to enable logs from WifiStateMachines */ |
| // enableDebugLogs(); |
| |
| installWlanWifiNative(mWifiNative); |
| mWifiMonitor = new MockWifiMonitor(); |
| |
| FrameworkFacade factory = getFrameworkFacade(); |
| Context context = getContext(); |
| |
| Resources resources = getMockResources(); |
| when(context.getResources()).thenReturn(resources); |
| |
| when(factory.getIntegerSetting(context, |
| Settings.Global.WIFI_FREQUENCY_BAND, |
| WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn( |
| WifiManager.WIFI_FREQUENCY_BAND_AUTO); |
| |
| when(factory.makeApConfigStore(Mockito.eq(context), any(Handler.class))) |
| .thenCallRealMethod(); |
| |
| when(factory.makeSupplicantStateTracker( |
| any(Context.class), any(WifiStateMachine.class), any(WifiConfigStore.class), |
| any(Handler.class))).thenReturn(mSupplicantStateTracker); |
| |
| mWsm = new WifiStateMachine(context, null, factory); |
| mWsmThread = getWsmHandlerThread(mWsm); |
| |
| final Object sync = new Object(); |
| synchronized (sync) { |
| mSyncThread = new HandlerThread("SynchronizationThread"); |
| final AsyncChannel channel = new AsyncChannel(); |
| mSyncThread.start(); |
| Handler handler = new Handler(mSyncThread.getLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: |
| if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { |
| mWsmAsyncChannel = channel; |
| synchronized (sync) { |
| sync.notifyAll(); |
| Log.d(TAG, "Successfully connected " + this); |
| } |
| } else { |
| Log.d(TAG, "Failed to connect Command channel " + this); |
| } |
| break; |
| case AsyncChannel.CMD_CHANNEL_DISCONNECTED: |
| Log.d(TAG, "Command channel disconnected" + this); |
| break; |
| } |
| } |
| }; |
| |
| channel.connect(context, handler, mWsm.getMessenger()); |
| sync.wait(); |
| } |
| |
| /* Now channel is supposed to be connected */ |
| } |
| |
| @After |
| public void cleanUp() throws Exception { |
| |
| if (mSyncThread != null) stopLooper(mSyncThread.getLooper()); |
| if (mWsmThread != null) stopLooper(mWsmThread.getLooper()); |
| if (mP2pThread != null) stopLooper(mP2pThread.getLooper()); |
| |
| mWsmThread = null; |
| mP2pThread = null; |
| mSyncThread = null; |
| mWsmAsyncChannel = null; |
| mWsm = null; |
| } |
| |
| @Test |
| public void createNew() throws Exception { |
| assertEquals("InitialState", getCurrentState().getName()); |
| |
| mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED); |
| wait(200); |
| assertEquals("InitialState", getCurrentState().getName()); |
| } |
| |
| @Test |
| public void loadComponents() throws Exception { |
| |
| when(mWifiNative.loadDriver()).thenReturn(true); |
| when(mWifiNative.startHal()).thenReturn(true); |
| when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true); |
| |
| mWsm.setSupplicantRunning(true); |
| wait(200); |
| assertEquals("SupplicantStartingState", getCurrentState().getName()); |
| |
| when(mWifiNative.setBand(anyInt())).thenReturn(true); |
| when(mWifiNative.setDeviceName(anyString())).thenReturn(true); |
| when(mWifiNative.setManufacturer(anyString())).thenReturn(true); |
| when(mWifiNative.setModelName(anyString())).thenReturn(true); |
| when(mWifiNative.setModelNumber(anyString())).thenReturn(true); |
| when(mWifiNative.setSerialNumber(anyString())).thenReturn(true); |
| when(mWifiNative.setConfigMethods(anyString())).thenReturn(true); |
| when(mWifiNative.setDeviceType(anyString())).thenReturn(true); |
| when(mWifiNative.setSerialNumber(anyString())).thenReturn(true); |
| when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true); |
| when(mWifiNative.enableBackgroundScan(anyBoolean())).thenReturn(true); |
| |
| mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT); |
| wait(200); |
| assertEquals("DisconnectedState", getCurrentState().getName()); |
| } |
| |
| @Test |
| public void loadComponentsFailure() throws Exception { |
| when(mWifiNative.loadDriver()).thenReturn(false); |
| when(mWifiNative.startHal()).thenReturn(false); |
| when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(false); |
| |
| mWsm.setSupplicantRunning(true); |
| wait(200); |
| assertEquals("InitialState", getCurrentState().getName()); |
| |
| when(mWifiNative.loadDriver()).thenReturn(true); |
| mWsm.setSupplicantRunning(true); |
| wait(200); |
| assertEquals("InitialState", getCurrentState().getName()); |
| |
| when(mWifiNative.startHal()).thenReturn(true); |
| mWsm.setSupplicantRunning(true); |
| wait(200); |
| assertEquals("InitialState", getCurrentState().getName()); |
| } |
| |
| @Test |
| public void addNetwork() throws Exception { |
| |
| loadComponents(); |
| |
| final HashMap<String, String> nameToValue = new HashMap<String, String>(); |
| |
| when(mWifiNative.addNetwork()).thenReturn(0); |
| when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).then( |
| new Answer<Boolean>() { |
| @Override |
| public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable { |
| Object args[] = invocationOnMock.getArguments(); |
| Integer netId = (Integer) args[0]; |
| String name = (String) args[1]; |
| String value = (String) args[2]; |
| if (netId != 0) { |
| Log.d(TAG, "Can't set var " + name + " for " + netId); |
| return false; |
| } |
| |
| Log.d(TAG, "Setting var " + name + " to " + value + " for " + netId); |
| nameToValue.put(name, value); |
| return true; |
| } |
| }); |
| when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject())) |
| .then(new Answer<Boolean>() { |
| @Override |
| public Boolean answer(InvocationOnMock invocationOnMock) |
| throws Throwable { |
| Object args[] = invocationOnMock.getArguments(); |
| Integer netId = (Integer) args[0]; |
| String name = (String) args[1]; |
| if (netId != 0) { |
| Log.d(TAG, "Can't set extra " + name + " for " + netId); |
| return false; |
| } |
| |
| Log.d(TAG, "Setting extra for " + netId); |
| return true; |
| } |
| }); |
| |
| when(mWifiNative.getNetworkVariable(anyInt(), anyString())).then( |
| new Answer<String>() { |
| @Override |
| public String answer(InvocationOnMock invocationOnMock) throws Throwable { |
| Object args[] = invocationOnMock.getArguments(); |
| Integer netId = (Integer) args[0]; |
| String name = (String) args[1]; |
| if (netId != 0) { |
| Log.d(TAG, "Can't find var " + name + " for " + netId); |
| return null; |
| } |
| String value = nameToValue.get(args[1]); |
| if (value != null) { |
| Log.d(TAG, "Returning var " + name + " to " + value + " for " + netId); |
| } else { |
| Log.d(TAG, "Can't find var " + name + " for " + netId); |
| } |
| return value; |
| } |
| }); |
| |
| WifiConfiguration config = new WifiConfiguration(); |
| config.SSID = sSSID; |
| config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); |
| mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config); |
| wait(200); |
| |
| verify(mWifiNative).addNetwork(); |
| verify(mWifiNative).setNetworkVariable(0, "ssid", sHexSSID); |
| |
| List<WifiConfiguration> configs = mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel); |
| assertEquals(1, configs.size()); |
| |
| WifiConfiguration config2 = configs.get(0); |
| assertEquals("\"GoogleGuest\"", config2.SSID); |
| assertTrue(config2.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)); |
| } |
| |
| @Test |
| public void scan() throws Exception { |
| |
| addNetwork(); |
| |
| mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE); |
| |
| mWsm.startScan(-1, 0, null, null); |
| wait(200); |
| |
| verify(mWifiNative).scan(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, null); |
| |
| when(mWifiNative.getScanResults()).thenReturn(getMockScanResults()); |
| mWsm.sendMessage(WifiMonitor.SCAN_RESULTS_EVENT); |
| |
| wait(200); |
| List<ScanResult> results = mWsm.syncGetScanResultsList(); |
| assertEquals(8, results.size()); |
| } |
| |
| @Test |
| public void connect() throws Exception { |
| |
| addNetwork(); |
| |
| mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE); |
| mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true); |
| wait(200); |
| |
| verify(mWifiNative).enableNetwork(0, true); |
| |
| mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID); |
| mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0, |
| new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED)); |
| wait(200); |
| |
| assertEquals("ObtainingIpState", getCurrentState().getName()); |
| |
| DhcpResults dhcpResults = new DhcpResults(); |
| dhcpResults.setGateway("1.2.3.4"); |
| dhcpResults.setIpAddress("192.168.1.100", 0); |
| dhcpResults.addDns("8.8.8.8"); |
| dhcpResults.setLeaseDuration(3600); |
| |
| mWsm.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION, DhcpStateMachine.DHCP_SUCCESS, 0, |
| dhcpResults); |
| wait(200); |
| |
| assertEquals("ConnectedState", getCurrentState().getName()); |
| } |
| |
| @Test |
| public void disconnect() throws Exception { |
| connect(); |
| |
| mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, -1, 3, "01:02:03:04:05:06"); |
| mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0, |
| new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED)); |
| wait(200); |
| |
| assertEquals("DisconnectedState", getCurrentState().getName()); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |