diff options
-rw-r--r-- | service/java/com/android/server/wifi/WifiStateMachinePrime.java | 296 | ||||
-rw-r--r-- | tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java | 300 |
2 files changed, 578 insertions, 18 deletions
diff --git a/service/java/com/android/server/wifi/WifiStateMachinePrime.java b/service/java/com/android/server/wifi/WifiStateMachinePrime.java index 33cccbb88..dbb826293 100644 --- a/service/java/com/android/server/wifi/WifiStateMachinePrime.java +++ b/service/java/com/android/server/wifi/WifiStateMachinePrime.java @@ -16,8 +16,17 @@ package com.android.server.wifi; +import android.net.wifi.IApInterface; +import android.net.wifi.IWificond; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.INetworkManagementService; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -32,8 +41,99 @@ public class WifiStateMachinePrime { private ModeStateMachine mModeStateMachine; - WifiStateMachinePrime() { - mModeStateMachine = new ModeStateMachine(TAG); + private final WifiInjector mWifiInjector; + private final Looper mLooper; + private final INetworkManagementService mNMService; + + private IWificond mWificond; + + /* The base for wifi message types */ + static final int BASE = Protocol.BASE_WIFI; + + /* Start the soft access point */ + static final int CMD_START_AP = BASE + 21; + /* Indicates soft ap start failed */ + static final int CMD_START_AP_FAILURE = BASE + 22; + /* Stop the soft access point */ + static final int CMD_STOP_AP = BASE + 23; + /* Soft access point teardown is completed. */ + static final int CMD_AP_STOPPED = BASE + 24; + + WifiStateMachinePrime(WifiInjector wifiInjector, + Looper looper, + INetworkManagementService nmService) { + mWifiInjector = wifiInjector; + mLooper = looper; + mNMService = nmService; + + // Clean up existing interfaces in wificond. + // This ensures that the framework and wificond are in a consistent state after a framework + // restart. + try { + mWificond = mWifiInjector.makeWificond(); + if (mWificond != null) { + mWificond.tearDownInterfaces(); + } + } catch (RemoteException e) { + Log.e(TAG, "wificond died during framework startup"); + } + } + + /** + * Method to switch wifi into client mode where connections to configured networks will be + * attempted. + */ + public void enterClientMode() { + changeMode(ModeStateMachine.CMD_START_CLIENT_MODE); + } + + /** + * Method to switch wifi into scan only mode where network connection attempts will not be made. + * + * This mode is utilized by location scans. If wifi is disabled by a user, but they have + * previously configured their device to perform location scans, this mode allows wifi to + * fulfill the location scan requests but will not be used for connectivity. + */ + public void enterScanOnlyMode() { + changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE); + } + + /** + * Method to enable soft ap for wifi hotspot. + */ + public void enterSoftAPMode() { + changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE); + } + + /** + * Method to fully disable wifi. + * + * This mode will completely shut down wifi and will not perform any network scans. + */ + public void disableWifi() { + changeMode(ModeStateMachine.CMD_DISABLE_WIFI); + } + + protected String getCurrentMode() { + if (mModeStateMachine != null) { + return mModeStateMachine.getCurrentMode(); + } + return "WifiDisabledState"; + } + + private void changeMode(int newMode) { + if (mModeStateMachine == null) { + if (newMode == ModeStateMachine.CMD_DISABLE_WIFI) { + // command is to disable wifi, but it is already disabled. + Log.e(TAG, "Received call to disable wifi when it is already disabled."); + return; + } + // state machine was not initialized yet, we must be starting up + mModeStateMachine = new ModeStateMachine(newMode); + } else { + // we have a current state, go ahead and prep for the new state + mModeStateMachine.sendMessage(newMode); + } } private class ModeStateMachine extends StateMachine { @@ -43,7 +143,6 @@ public class WifiStateMachinePrime { public static final int CMD_START_SOFT_AP_MODE = 2; public static final int CMD_DISABLE_WIFI = 3; - // Create the base modes for WSM. private final State mClientModeState = new ClientModeState(); private final State mScanOnlyModeState = new ScanOnlyModeState(); @@ -55,8 +154,8 @@ public class WifiStateMachinePrime { private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState(); private final State mSoftAPModeActiveState = new SoftAPModeActiveState(); - ModeStateMachine(String name) { - super(name); + ModeStateMachine(int initialMode) { + super(TAG, mLooper); // CHECKSTYLE:OFF IndentationCheck addState(mClientModeState); @@ -67,54 +166,168 @@ public class WifiStateMachinePrime { addState(mSoftAPModeActiveState, mSoftAPModeState); addState(mWifiDisabledState); // CHECKSTYLE:ON IndentationCheck + + State startingState; + switch(initialMode) { + case CMD_START_CLIENT_MODE: + startingState = mClientModeState; + break; + case CMD_START_SCAN_ONLY_MODE: + startingState = mScanOnlyModeState; + break; + case CMD_START_SOFT_AP_MODE: + startingState = mSoftAPModeState; + break; + default: + Log.e(TAG, "Attempting to start WifiStateMachinePrime " + + "in an invalid operating mode: " + initialMode); + throw new IllegalArgumentException("Invalid wifi operating mode"); + } + Log.d(TAG, "Switching from WifiDisabled to " + startingState.getName()); + setInitialState(startingState); + start(); } private String getCurrentMode() { - try { - return getCurrentState().getName(); - } catch (NullPointerException e) { - // current state is not set - return null; + return getCurrentState().getName(); + } + + private boolean checkForAndHandleModeChange(Message message) { + switch(message.what) { + case ModeStateMachine.CMD_START_CLIENT_MODE: + Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode"); + mModeStateMachine.transitionTo(mClientModeState); + break; + case ModeStateMachine.CMD_START_SCAN_ONLY_MODE: + Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode"); + mModeStateMachine.transitionTo(mScanOnlyModeState); + break; + case ModeStateMachine.CMD_START_SOFT_AP_MODE: + Log.d(TAG, "Switching from " + getCurrentMode() + " to SoftApMode"); + mModeStateMachine.transitionTo(mSoftAPModeState); + break; + case ModeStateMachine.CMD_DISABLE_WIFI: + Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled"); + mModeStateMachine.transitionTo(mWifiDisabledState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + private void tearDownInterfaces() { + if (mWificond != null) { + try { + mWificond.tearDownInterfaces(); + } catch (RemoteException e) { + // There is very little we can do here + Log.e(TAG, "Failed to tear down interfaces via wificond"); + } + mWificond = null; } + return; } class ClientModeState extends State { + @Override + public void enter() { + mWificond = mWifiInjector.makeWificond(); + } @Override public boolean processMessage(Message message) { + if (checkForAndHandleModeChange(message)) { + return HANDLED; + } return NOT_HANDLED; } @Override public void exit() { - + tearDownInterfaces(); } } class ScanOnlyModeState extends State { + @Override + public void enter() { + } @Override public boolean processMessage(Message message) { - // handle Mode changes and any events requiring setup or restarting services - + if (checkForAndHandleModeChange(message)) { + return HANDLED; + } return NOT_HANDLED; } @Override public void exit() { - // tear down running services + // Do not tear down interfaces yet since this mode is not actively controlled or + // used in tests at this time. + // tearDownInterfaces(); } } class SoftAPModeState extends State { + IApInterface mApInterface = null; + + @Override + public void enter() { + mApInterface = null; + mWificond = mWifiInjector.makeWificond(); + if (mWificond == null) { + Log.e(TAG, "Failed to get reference to wificond"); + mModeStateMachine.sendMessage(CMD_START_AP_FAILURE); + return; + } + + try { + mApInterface = mWificond.createApInterface(); + } catch (RemoteException e1) { } + + if (mApInterface == null) { + Log.e(TAG, "Could not get IApInterface instance from wificond"); + mModeStateMachine.sendMessage(CMD_START_AP_FAILURE); + return; + } + mModeStateMachine.sendMessage(CMD_START_AP); + } @Override public boolean processMessage(Message message) { - return NOT_HANDLED; + if (checkForAndHandleModeChange(message)) { + return HANDLED; + } + + switch(message.what) { + case CMD_START_AP: + Log.d(TAG, "Transitioning to softapmodeactivestate"); + mModeStateMachine.transitionTo(mSoftAPModeActiveState); + break; + case CMD_STOP_AP: + // not in active state, nothing to stop. + break; + case CMD_START_AP_FAILURE: + Log.e(TAG, "Failed to start SoftApMode. Wait for next mode command."); + break; + case CMD_AP_STOPPED: + Log.d(TAG, "SoftApModeActiveState stopped. Wait for next mode command."); + break; + default: + return NOT_HANDLED; + } + return HANDLED; } @Override public void exit() { + tearDownInterfaces(); + } + + protected IApInterface getInterface() { + return mApInterface; } } @@ -163,11 +376,58 @@ public class WifiStateMachinePrime { } class SoftAPModeActiveState extends ModeActiveState { + private class SoftApListener implements SoftApManager.Listener { + @Override + public void onStateChanged(int state, int reason) { + if (state == WifiManager.WIFI_AP_STATE_DISABLED) { + mModeStateMachine.sendMessage(CMD_AP_STOPPED); + } else if (state == WifiManager.WIFI_AP_STATE_FAILED) { + mModeStateMachine.sendMessage(CMD_START_AP_FAILURE); + } + } + } + @Override public void enter() { - // The SoftApManager is not empty at this time, will populate in later CLs. - //this.mActiveModeManager = new SoftApManager(); + Log.d(TAG, "Entering SoftApModeActiveState"); + final Message message = mModeStateMachine.getCurrentMessage(); + if (message.what != CMD_START_AP) { + throw new RuntimeException("Illegal transition to SoftApState: " + message); + } + // The WifiConfiguration is generally going to be null to indicate that the + // currently saved config in WifiApConfigManager should be used. When the config is + // not null, it will be saved in the WifiApConfigManager. This save is performed in + // the constructor of SoftApManager. + WifiConfiguration config = (WifiConfiguration) message.obj; + this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService, + new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(), + config); + mActiveModeManager.start(); + } + + @Override + public boolean processMessage(Message message) { + switch(message.what) { + case CMD_START_AP: + Log.d(TAG, "Received CMD_START_AP with SoftApMode already active. drop"); + break; + case CMD_STOP_AP: + mActiveModeManager.stop(); + break; + case CMD_START_AP_FAILURE: + Log.d(TAG, "Failed to start SoftApMode. Return to SoftApMode (inactive)."); + mModeStateMachine.transitionTo(mSoftAPModeState); + break; + case CMD_AP_STOPPED: + Log.d(TAG, "SoftApModeActiveState stopped." + + " Return to SoftApMode (inactive)."); + mModeStateMachine.transitionTo(mSoftAPModeState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; } } - } // class ModeStateMachine + } // class ModeStateMachine } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java new file mode 100644 index 000000000..cf20ecc56 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2016 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.mockito.Mockito.*; + +import android.net.wifi.IApInterface; +import android.net.wifi.IWificond; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.INetworkManagementService; +import android.os.test.TestLooper; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * Unit tests for {@link com.android.server.wifi.WifiStateMachinePrime}. + */ +@SmallTest +public class WifiStateMachinePrimeTest { + public static final String TAG = "WifiStateMachinePrimeTest"; + + private static final String CLIENT_MODE_STATE_STRING = "ClientModeState"; + private static final String SCAN_ONLY_MODE_STATE_STRING = "ScanOnlyModeState"; + private static final String SOFT_AP_MODE_STATE_STRING = "SoftAPModeState"; + private static final String WIFI_DISABLED_STATE_STRING = "WifiDisabledState"; + private static final String CLIENT_MODE_ACTIVE_STATE_STRING = "ClientModeActiveState"; + private static final String SCAN_ONLY_MODE_ACTIVE_STATE_STRING = "ScanOnlyModeActiveState"; + private static final String SOFT_AP_MODE_ACTIVE_STATE_STRING = "SoftAPModeActiveState"; + + @Mock WifiInjector mWifiInjector; + TestLooper mLooper; + @Mock IWificond mWificond; + @Mock IApInterface mApInterface; + @Mock INetworkManagementService mNMService; + @Mock SoftApManager mSoftApManager; + SoftApManager.Listener mSoftApListener; + @Mock WifiConfiguration mApConfig; + WifiStateMachinePrime mWifiStateMachinePrime; + + /** + * Set up the test environment. + */ + @Before + public void setUp() throws Exception { + Log.d(TAG, "Setting up ..."); + + MockitoAnnotations.initMocks(this); + mLooper = new TestLooper(); + + mWifiInjector = mock(WifiInjector.class); + mWifiStateMachinePrime = createWifiStateMachinePrime(); + } + + private WifiStateMachinePrime createWifiStateMachinePrime() { + when(mWifiInjector.makeWificond()).thenReturn(null); + return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService); + } + + /** + * Clean up after tests - explicitly set tested object to null. + */ + @After + public void cleanUp() throws Exception { + mWifiStateMachinePrime = null; + } + + /** + * Helper method to enter the SoftApActiveMode for WifiStateMachinePrime. + * + * This method puts the test object into the correct state and verifies steps along the way. + */ + private void enterSoftApActiveMode() throws Exception { + String fromState = mWifiStateMachinePrime.getCurrentMode(); + when(mWifiInjector.makeWificond()).thenReturn(mWificond); + when(mWificond.createApInterface()).thenReturn(mApInterface); + doAnswer( + new Answer<Object>() { + public SoftApManager answer(InvocationOnMock invocation) { + mSoftApListener = (SoftApManager.Listener) invocation.getArguments()[1]; + return mSoftApManager; + } + }).when(mWifiInjector).makeSoftApManager(any(INetworkManagementService.class), + any(SoftApManager.Listener.class), + any(IApInterface.class), + any(WifiConfiguration.class)); + mWifiStateMachinePrime.enterSoftAPMode(); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + Log.e("WifiStateMachinePrimeTest", "check fromState: " + fromState); + if (!fromState.equals(WIFI_DISABLED_STATE_STRING)) { + verify(mWificond).tearDownInterfaces(); + } + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + verify(mSoftApManager).start(); + } + + /** + * Test that when a new instance of WifiStateMachinePrime is created, any existing interfaces in + * the retrieved Wificond instance are cleaned up. + * Expectations: When the new WifiStateMachinePrime instance is created a call to + * Wificond.tearDownInterfaces() is made. + */ + @Test + public void testWificondExistsOnStartup() throws Exception { + when(mWifiInjector.makeWificond()).thenReturn(mWificond); + WifiStateMachinePrime testWifiStateMachinePrime = + new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService); + verify(mWificond).tearDownInterfaces(); + } + + /** + * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from the + * WifiDisabled state. + */ + @Test + public void testEnterSoftApModeFromDisabled() throws Exception { + enterSoftApActiveMode(); + } + + /** + * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state. + * Expectations: When going from one state to another, any interfaces that are still up are torn + * down. + */ + @Test + public void testEnterSoftApModeFromDifferentState() throws Exception { + when(mWifiInjector.makeWificond()).thenReturn(mWificond); + mWifiStateMachinePrime.enterClientMode(); + mLooper.dispatchNext(); + assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + enterSoftApActiveMode(); + } + + /** + * Test that we can disable wifi fully from the SoftApModeActiveState. + */ + @Test + public void testDisableWifiFromSoftApModeActiveState() throws Exception { + enterSoftApActiveMode(); + + mWifiStateMachinePrime.disableWifi(); + mLooper.dispatchNext(); + verify(mSoftApManager).stop(); + verify(mWificond).tearDownInterfaces(); + assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + } + + /** + * Test that we can disable wifi fully from the SoftApModeState. + */ + @Test + public void testDisableWifiFromSoftApModeState() throws Exception { + // Use a failure getting wificond to stay in the SoftAPModeState + when(mWifiInjector.makeWificond()).thenReturn(null); + mWifiStateMachinePrime.enterSoftAPMode(); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + + mWifiStateMachinePrime.disableWifi(); + mLooper.dispatchNext(); + // mWificond will be null due to this test, no call to tearDownInterfaces here. + assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + } + + /** + * Test that we can switch from SoftApActiveMode to another mode. + * Expectation: When switching out of SoftApModeActiveState we stop the SoftApManager and tear + * down existing interfaces. + */ + @Test + public void testSwitchModeWhenSoftApActiveMode() throws Exception { + enterSoftApActiveMode(); + + mWifiStateMachinePrime.enterClientMode(); + mLooper.dispatchNext(); + verify(mSoftApManager).stop(); + verify(mWificond).tearDownInterfaces(); + assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + } + + /** + * Test that we do not attempt to enter SoftApModeActiveState when we cannot get a reference to + * wificond. + * Expectations: After a failed attempt to get wificond from WifiInjector, we should remain in + * the SoftApModeState. + */ + @Test + public void testWificondNullWhenSwitchingToApMode() throws Exception { + when(mWifiInjector.makeWificond()).thenReturn(null); + mWifiStateMachinePrime.enterSoftAPMode(); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + } + + /** + * Test that we do not attempt to enter SoftApModeActiveState when we cannot get an ApInterface + * from wificond. + * Expectations: After a failed attempt to get an ApInterface from WifiInjector, we should + * remain in the SoftApModeState. + */ + @Test + public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception { + when(mWifiInjector.makeWificond()).thenReturn(mWificond); + when(mWificond.createApInterface()).thenReturn(null); + mWifiStateMachinePrime.enterSoftAPMode(); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + } + + /** + * Test that we do can enter the SoftApModeActiveState if we are already in the SoftApModeState. + * Expectations: We should exit the current SoftApModeState and re-enter before successfully + * entering the SoftApModeActiveState. + */ + @Test + public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception { + when(mWifiInjector.makeWificond()).thenReturn(mWificond); + when(mWificond.createApInterface()).thenReturn(null); + mWifiStateMachinePrime.enterSoftAPMode(); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + + enterSoftApActiveMode(); + verify(mWificond).tearDownInterfaces(); + } + + /** + * Test that we return to the SoftApModeState after a failure is reported when in the + * SoftApModeActiveState. + * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager. + */ + @Test + public void testSoftApFailureWhenActive() throws Exception { + enterSoftApActiveMode(); + // now inject failure through the SoftApManager.Listener + mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + verify(mSoftApManager).stop(); + } + + /** + * Test that we return to the SoftApModeState after the SoftApManager is stopped in the + * SoftApModeActiveState. + * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager. + */ + @Test + public void testSoftApDisabledWhenActive() throws Exception { + enterSoftApActiveMode(); + // now inject failure through the SoftApManager.Listener + mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0); + mLooper.dispatchNext(); + assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode()); + verify(mSoftApManager).stop(); + } + + /** + * Test that we safely disable wifi if it is already disabled. + * Expectations: We should not interact with wificond since we should have already cleaned up + * everything. + */ + @Test + public void disableWifiWhenAlreadyOff() throws Exception { + verifyNoMoreInteractions(mWificond); + mWifiStateMachinePrime.disableWifi(); + } +} |