Merge changes from topic 'tethering-ownership' into nyc-mr1-dev
* changes: Tethering: Own WiFi tethering state and lifetime Use an ArrayMap instead of HashMap to track tether interfaces Consolidate cleanup logic in TetherInterfaceSM.TetheredState Annotate TetherInterfaceStateMachineTest for APCT Rely on Tethering mutex for TetherInterfaceSM Rename TetherInterfaceSM to TetherInterfaceStateMachine Remove dead code from TetherInterfaceSM Remove transient StartingState from TetherInterfaceSM Expand test coverage of TetherInterfaceSM Add demonstration unittest for TetherInterfaceSM Extract TetherInterfaceSM to its own class. Make Tethering.TetherInterfaceSM more self contained Clean up class members in Tethering.TetherInterfaceSM Fix trivial warnings in Tethering.java
This commit is contained in:
committed by
Android (Google) Code Review
commit
281ec3272f
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.connectivity.tethering;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*
|
||||
* Interface with methods necessary to notify that a given interface is ready for tethering.
|
||||
*/
|
||||
public interface IControlsTethering {
|
||||
void sendTetherStateChangedBroadcast();
|
||||
void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceStateMachine who);
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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.connectivity.tethering;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.InterfaceConfiguration;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkUtils;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.util.IState;
|
||||
import com.android.internal.util.MessageUtils;
|
||||
import com.android.internal.util.Protocol;
|
||||
import com.android.internal.util.State;
|
||||
import com.android.internal.util.StateMachine;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*
|
||||
* Tracks the eligibility of a given network interface for tethering.
|
||||
*/
|
||||
public class TetherInterfaceStateMachine extends StateMachine {
|
||||
private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
|
||||
private static final int USB_PREFIX_LENGTH = 24;
|
||||
private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
|
||||
private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
|
||||
|
||||
private final static String TAG = "TetherInterfaceSM";
|
||||
private final static boolean DBG = false;
|
||||
private final static boolean VDBG = false;
|
||||
private static final Class[] messageClasses = {
|
||||
TetherInterfaceStateMachine.class
|
||||
};
|
||||
private static final SparseArray<String> sMagicDecoderRing =
|
||||
MessageUtils.findMessageNames(messageClasses);
|
||||
|
||||
private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
|
||||
// request from the user that it wants to tether
|
||||
public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
|
||||
// request from the user that it wants to untether
|
||||
public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
|
||||
// notification that this interface is down
|
||||
public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
|
||||
// notification from the master SM that it had trouble enabling IP Forwarding
|
||||
public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
|
||||
// notification from the master SM that it had trouble disabling IP Forwarding
|
||||
public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
|
||||
// notification from the master SM that it had trouble starting tethering
|
||||
public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
|
||||
// notification from the master SM that it had trouble stopping tethering
|
||||
public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
|
||||
// notification from the master SM that it had trouble setting the DNS forwarders
|
||||
public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
|
||||
// the upstream connection has changed
|
||||
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
|
||||
|
||||
private final State mInitialState;
|
||||
private final State mTetheredState;
|
||||
private final State mUnavailableState;
|
||||
|
||||
private final INetworkManagementService mNMService;
|
||||
private final INetworkStatsService mStatsService;
|
||||
private final IControlsTethering mTetherController;
|
||||
|
||||
private final String mIfaceName;
|
||||
private final int mInterfaceType;
|
||||
|
||||
private int mLastError;
|
||||
private String mMyUpstreamIfaceName; // may change over time
|
||||
|
||||
public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
|
||||
INetworkManagementService nMService, INetworkStatsService statsService,
|
||||
IControlsTethering tetherController) {
|
||||
super(ifaceName, looper);
|
||||
mNMService = nMService;
|
||||
mStatsService = statsService;
|
||||
mTetherController = tetherController;
|
||||
mIfaceName = ifaceName;
|
||||
mInterfaceType = interfaceType;
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
|
||||
|
||||
mInitialState = new InitialState();
|
||||
addState(mInitialState);
|
||||
mTetheredState = new TetheredState();
|
||||
addState(mTetheredState);
|
||||
mUnavailableState = new UnavailableState();
|
||||
addState(mUnavailableState);
|
||||
|
||||
setInitialState(mInitialState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String res = new String();
|
||||
res += mIfaceName + " - ";
|
||||
IState current = getCurrentState();
|
||||
if (current == mInitialState) res += "InitialState";
|
||||
if (current == mTetheredState) res += "TetheredState";
|
||||
if (current == mUnavailableState) res += "UnavailableState";
|
||||
if (isAvailable()) res += " - Available";
|
||||
if (isTethered()) res += " - Tethered";
|
||||
res += " - lastError =" + getLastError();
|
||||
return res;
|
||||
}
|
||||
|
||||
public int getLastError() {
|
||||
return mLastError;
|
||||
}
|
||||
|
||||
private void setLastError(int error) {
|
||||
mLastError = error;
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return getCurrentState() == mInitialState;
|
||||
}
|
||||
|
||||
public boolean isTethered() {
|
||||
return getCurrentState() == mTetheredState;
|
||||
}
|
||||
|
||||
public boolean isErrored() {
|
||||
return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
|
||||
}
|
||||
|
||||
// configured when we start tethering and unconfig'd on error or conclusion
|
||||
private boolean configureIfaceIp(boolean enabled) {
|
||||
if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
|
||||
|
||||
String ipAsString = null;
|
||||
int prefixLen = 0;
|
||||
if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
|
||||
ipAsString = USB_NEAR_IFACE_ADDR;
|
||||
prefixLen = USB_PREFIX_LENGTH;
|
||||
} else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
|
||||
ipAsString = WIFI_HOST_IFACE_ADDR;
|
||||
prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
|
||||
} else {
|
||||
// Nothing to do, BT does this elsewhere.
|
||||
return true;
|
||||
}
|
||||
|
||||
InterfaceConfiguration ifcg = null;
|
||||
try {
|
||||
ifcg = mNMService.getInterfaceConfig(mIfaceName);
|
||||
if (ifcg != null) {
|
||||
InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
|
||||
ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
|
||||
if (enabled) {
|
||||
ifcg.setInterfaceUp();
|
||||
} else {
|
||||
ifcg.setInterfaceDown();
|
||||
}
|
||||
ifcg.clearFlag("running");
|
||||
mNMService.setInterfaceConfig(mIfaceName, ifcg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error configuring interface " + mIfaceName, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void maybeLogMessage(State state, int what) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, state.getName() + " got " +
|
||||
sMagicDecoderRing.get(what, Integer.toString(what)));
|
||||
}
|
||||
}
|
||||
|
||||
class InitialState extends State {
|
||||
@Override
|
||||
public void enter() {
|
||||
mTetherController.sendTetherStateChangedBroadcast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processMessage(Message message) {
|
||||
maybeLogMessage(this, message.what);
|
||||
boolean retValue = true;
|
||||
switch (message.what) {
|
||||
case CMD_TETHER_REQUESTED:
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
|
||||
mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceStateMachine.this);
|
||||
transitionTo(mTetheredState);
|
||||
break;
|
||||
case CMD_INTERFACE_DOWN:
|
||||
transitionTo(mUnavailableState);
|
||||
break;
|
||||
default:
|
||||
retValue = false;
|
||||
break;
|
||||
}
|
||||
return retValue;
|
||||
}
|
||||
}
|
||||
|
||||
class TetheredState extends State {
|
||||
@Override
|
||||
public void enter() {
|
||||
if (!configureIfaceIp(true)) {
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
|
||||
transitionTo(mInitialState);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mNMService.tetherInterface(mIfaceName);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error Tethering: " + e.toString());
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
|
||||
transitionTo(mInitialState);
|
||||
return;
|
||||
}
|
||||
if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
|
||||
mTetherController.sendTetherStateChangedBroadcast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
mTetherController.notifyInterfaceTetheringReadiness(false,
|
||||
TetherInterfaceStateMachine.this);
|
||||
|
||||
// Note that at this point, we're leaving the tethered state. We can fail any
|
||||
// of these operations, but it doesn't really change that we have to try them
|
||||
// all in sequence.
|
||||
cleanupUpstream();
|
||||
|
||||
try {
|
||||
mNMService.untetherInterface(mIfaceName);
|
||||
} catch (Exception ee) {
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
|
||||
Log.e(TAG, "Failed to untether interface: " + ee.toString());
|
||||
}
|
||||
|
||||
configureIfaceIp(false);
|
||||
}
|
||||
|
||||
private void cleanupUpstream() {
|
||||
if (mMyUpstreamIfaceName != null) {
|
||||
// note that we don't care about errors here.
|
||||
// sometimes interfaces are gone before we get
|
||||
// to remove their rules, which generates errors.
|
||||
// just do the best we can.
|
||||
try {
|
||||
// about to tear down NAT; gather remaining statistics
|
||||
mStatsService.forceUpdate();
|
||||
} catch (Exception e) {
|
||||
if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
|
||||
}
|
||||
try {
|
||||
mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
|
||||
} catch (Exception e) {
|
||||
if (VDBG) Log.e(
|
||||
TAG, "Exception in removeInterfaceForward: " + e.toString());
|
||||
}
|
||||
try {
|
||||
mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
|
||||
} catch (Exception e) {
|
||||
if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
|
||||
}
|
||||
mMyUpstreamIfaceName = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processMessage(Message message) {
|
||||
maybeLogMessage(this, message.what);
|
||||
boolean retValue = true;
|
||||
switch (message.what) {
|
||||
case CMD_TETHER_UNREQUESTED:
|
||||
transitionTo(mInitialState);
|
||||
if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
|
||||
break;
|
||||
case CMD_INTERFACE_DOWN:
|
||||
transitionTo(mUnavailableState);
|
||||
if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
|
||||
break;
|
||||
case CMD_TETHER_CONNECTION_CHANGED:
|
||||
String newUpstreamIfaceName = (String)(message.obj);
|
||||
if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
|
||||
(mMyUpstreamIfaceName != null &&
|
||||
mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
|
||||
if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
|
||||
break;
|
||||
}
|
||||
cleanupUpstream();
|
||||
if (newUpstreamIfaceName != null) {
|
||||
try {
|
||||
mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
|
||||
mNMService.startInterfaceForwarding(mIfaceName,
|
||||
newUpstreamIfaceName);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception enabling Nat: " + e.toString());
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
|
||||
transitionTo(mInitialState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
mMyUpstreamIfaceName = newUpstreamIfaceName;
|
||||
break;
|
||||
case CMD_IP_FORWARDING_ENABLE_ERROR:
|
||||
case CMD_IP_FORWARDING_DISABLE_ERROR:
|
||||
case CMD_START_TETHERING_ERROR:
|
||||
case CMD_STOP_TETHERING_ERROR:
|
||||
case CMD_SET_DNS_FORWARDERS_ERROR:
|
||||
setLastErrorAndTransitionToInitialState(
|
||||
ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
|
||||
break;
|
||||
default:
|
||||
retValue = false;
|
||||
break;
|
||||
}
|
||||
return retValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This state is terminal for the per interface state machine. At this
|
||||
* point, the master state machine should have removed this interface
|
||||
* specific state machine from its list of possible recipients of
|
||||
* tethering requests. The state machine itself will hang around until
|
||||
* the garbage collector finds it.
|
||||
*/
|
||||
class UnavailableState extends State {
|
||||
@Override
|
||||
public void enter() {
|
||||
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
|
||||
mTetherController.sendTetherStateChangedBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void setLastErrorAndTransitionToInitialState(int error) {
|
||||
setLastError(error);
|
||||
transitionTo(mInitialState);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
frameworks-base-testutils \
|
||||
services.core \
|
||||
services.devicepolicy \
|
||||
services.net \
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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.connectivity.tethering;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.InterfaceConfiguration;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.RemoteException;
|
||||
import android.os.test.TestLooper;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class TetherInterfaceStateMachineTest {
|
||||
private static final String IFACE_NAME = "testnet1";
|
||||
private static final String UPSTREAM_IFACE = "upstream0";
|
||||
private static final String UPSTREAM_IFACE2 = "upstream1";
|
||||
|
||||
@Mock private INetworkManagementService mNMService;
|
||||
@Mock private INetworkStatsService mStatsService;
|
||||
@Mock private IControlsTethering mTetherHelper;
|
||||
@Mock private InterfaceConfiguration mInterfaceConfiguration;
|
||||
|
||||
private final TestLooper mLooper = new TestLooper();
|
||||
private TetherInterfaceStateMachine mTestedSm;
|
||||
|
||||
private void initStateMachine(int interfaceType) throws Exception {
|
||||
mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
|
||||
mNMService, mStatsService, mTetherHelper);
|
||||
mTestedSm.start();
|
||||
// Starting the state machine always puts us in a consistent state and notifies
|
||||
// the test of the world that we've changed from an unknown to available state.
|
||||
mLooper.dispatchAll();
|
||||
reset(mNMService, mStatsService, mTetherHelper);
|
||||
when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
|
||||
}
|
||||
|
||||
private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
|
||||
initStateMachine(interfaceType);
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
|
||||
if (upstreamIface != null) {
|
||||
dispatchTetherConnectionChanged(upstreamIface);
|
||||
}
|
||||
reset(mNMService, mStatsService, mTetherHelper);
|
||||
when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startsOutAvailable() {
|
||||
mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
|
||||
ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
|
||||
mTestedSm.start();
|
||||
mLooper.dispatchAll();
|
||||
assertTrue("Should start out available for tethering", mTestedSm.isAvailable());
|
||||
assertFalse("Should not be tethered initially", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors initially", mTestedSm.isErrored());
|
||||
verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDoNothingUntilRequested() throws Exception {
|
||||
initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
|
||||
final int [] NOOP_COMMANDS = {
|
||||
TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
|
||||
TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
|
||||
TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
|
||||
TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
|
||||
TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
|
||||
TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
|
||||
TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
|
||||
};
|
||||
for (int command : NOOP_COMMANDS) {
|
||||
// None of these commands should trigger us to request action from
|
||||
// the rest of the system.
|
||||
dispatchCommand(command);
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesImmediateInterfaceDown() throws Exception {
|
||||
initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
|
||||
verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable());
|
||||
assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors when the interface goes immediately down",
|
||||
mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeTethered() throws Exception {
|
||||
initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
|
||||
InOrder inOrder = inOrder(mTetherHelper, mNMService);
|
||||
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
|
||||
inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
|
||||
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
|
||||
assertTrue("Should be in a tethered state", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canUnrequestTethering() throws Exception {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
|
||||
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
|
||||
InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
|
||||
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
|
||||
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
|
||||
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
|
||||
assertFalse("Should not be tethered", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeTetheredAsUsb() throws Exception {
|
||||
initStateMachine(ConnectivityManager.TETHERING_USB);
|
||||
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
|
||||
InOrder inOrder = inOrder(mTetherHelper, mNMService);
|
||||
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
|
||||
inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
|
||||
inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
|
||||
inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
|
||||
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
|
||||
assertTrue("Should be in a tethered state", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesFirstUpstreamChange() throws Exception {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
|
||||
|
||||
// Telling the state machine about its upstream interface triggers a little more configuration.
|
||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
|
||||
InOrder inOrder = inOrder(mNMService);
|
||||
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
|
||||
inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
|
||||
assertTrue("Should be in a tethered state", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlesChangingUpstream() throws Exception {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
||||
|
||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
|
||||
InOrder inOrder = inOrder(mNMService, mStatsService);
|
||||
inOrder.verify(mStatsService).forceUpdate();
|
||||
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
|
||||
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
|
||||
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
|
||||
inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
|
||||
assertTrue("Should be in a tethered state", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canUnrequestTetheringWithUpstream() throws Exception {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
|
||||
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
|
||||
InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
|
||||
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
|
||||
inOrder.verify(mStatsService).forceUpdate();
|
||||
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
|
||||
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
|
||||
inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
|
||||
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
|
||||
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
|
||||
assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
|
||||
assertFalse("Should not be tethered", mTestedSm.isTethered());
|
||||
assertFalse("Should have no errors", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceDownLeadsToUnavailable() throws Exception {
|
||||
for (boolean shouldThrow : new boolean[]{true, false}) {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
|
||||
|
||||
if (shouldThrow) {
|
||||
doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
|
||||
}
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
|
||||
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
|
||||
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
|
||||
usbTeardownOrder.verify(mNMService).setInterfaceConfig(
|
||||
IFACE_NAME, mInterfaceConfiguration);
|
||||
verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
|
||||
assertFalse("Should not be available", mTestedSm.isAvailable());
|
||||
assertFalse("Should not be tethered", mTestedSm.isTethered());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usbShouldBeTornDownOnTetherError() throws Exception {
|
||||
initStateMachine(ConnectivityManager.TETHERING_USB);
|
||||
|
||||
doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
|
||||
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
|
||||
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
|
||||
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
|
||||
usbTeardownOrder.verify(mNMService).setInterfaceConfig(
|
||||
IFACE_NAME, mInterfaceConfiguration);
|
||||
// Initial call is when we transition to the tethered state on request.
|
||||
verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
|
||||
// And this call is to notify that we really aren't requested tethering.
|
||||
verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
|
||||
assertTrue("Expected to see an error reported", mTestedSm.isErrored());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTearDownUsbOnUpstreamError() throws Exception {
|
||||
initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
|
||||
|
||||
doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
|
||||
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
|
||||
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
|
||||
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
|
||||
usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
|
||||
verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the state machine under test, and run the event loop to idle.
|
||||
*
|
||||
* @param command One of the TetherInterfaceStateMachine.CMD_* constants.
|
||||
*/
|
||||
private void dispatchCommand(int command) {
|
||||
mTestedSm.sendMessage(command);
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special override to tell the state machine that the upstream interface has changed.
|
||||
*
|
||||
* @see #dispatchCommand(int)
|
||||
* @param upstreamIface String name of upstream interface (or null)
|
||||
*/
|
||||
private void dispatchTetherConnectionChanged(String upstreamIface) {
|
||||
mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
|
||||
upstreamIface);
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user