Merge changes from topic \'tethering-ownership\' into nyc-mr1-dev
am: 281ec3272f
Change-Id: Ia8b234bcfdd479ef1ecda6b9102cf7cd5b3f217c
This commit is contained in:
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