Merge changes from topic \'tethering-ownership\' into nyc-mr1-dev

am: 281ec3272f

Change-Id: Ia8b234bcfdd479ef1ecda6b9102cf7cd5b3f217c
This commit is contained in:
Christopher Wiley
2016-06-08 21:00:21 +00:00
committed by android-build-merger
5 changed files with 886 additions and 589 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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 \

View File

@@ -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();
}
}