Merge "Fix race conditions between Tethering and TetherInterfaceStateMachine" into nyc-mr1-dev

This commit is contained in:
TreeHugger Robot
2016-06-09 23:53:54 +00:00
committed by Android (Google) Code Review
4 changed files with 230 additions and 232 deletions

View File

@@ -81,8 +81,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -126,7 +124,18 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
private final INetworkStatsService mStatsService; private final INetworkStatsService mStatsService;
private final Looper mLooper; private final Looper mLooper;
private Map<String, TetherInterfaceStateMachine> mIfaces; // all tethered/tetherable ifaces private static class TetherState {
public final TetherInterfaceStateMachine mStateMachine;
public int mLastState;
public int mLastError;
public TetherState(TetherInterfaceStateMachine sm) {
mStateMachine = sm;
// Assume all state machines start out available and with no errors.
mLastState = IControlsTethering.STATE_AVAILABLE;
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
}
}
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver; private final BroadcastReceiver mStateReceiver;
@@ -174,7 +183,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
mPublicSync = new Object(); mPublicSync = new Object();
mIfaces = new ArrayMap<String, TetherInterfaceStateMachine>(); mTetherStates = new ArrayMap<>();
// make our own thread so we don't anr the system // make our own thread so we don't anr the system
mLooper = IoThread.get().getLooper(); mLooper = IoThread.get().getLooper();
@@ -255,22 +264,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
return; return;
} }
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (up) { if (up) {
if (sm == null) { if (tetherState == null) {
sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType, trackNewTetherableInterface(iface, interfaceType);
mNMService, mStatsService, this);
mIfaces.put(iface, sm);
sm.start();
} }
} else { } else {
if (interfaceType == ConnectivityManager.TETHERING_USB) { if (interfaceType == ConnectivityManager.TETHERING_USB) {
// ignore usb0 down after enabling RNDIS // ignore usb0 down after enabling RNDIS
// we will handle disconnect in interfaceRemoved instead // we will handle disconnect in interfaceRemoved instead
if (VDBG) Log.d(TAG, "ignore interface down for " + iface); if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
} else if (sm != null) { } else if (tetherState != null) {
sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); tetherState.mStateMachine.sendMessage(
mIfaces.remove(iface); TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
mTetherStates.remove(iface);
} }
} }
} }
@@ -329,15 +336,12 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
return; return;
} }
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (sm != null) { if (tetherState == null) {
trackNewTetherableInterface(iface, interfaceType);
} else {
if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
return;
} }
sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType,
mNMService, mStatsService, this);
mIfaces.put(iface, sm);
sm.start();
} }
} }
@@ -345,15 +349,15 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
public void interfaceRemoved(String iface) { public void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface); if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) { synchronized (mPublicSync) {
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (sm == null) { if (tetherState == null) {
if (VDBG) { if (VDBG) {
Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
} }
return; return;
} }
sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
mIfaces.remove(iface); mTetherStates.remove(iface);
} }
} }
@@ -582,19 +586,18 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
public int tether(String iface) { public int tether(String iface) {
if (DBG) Log.d(TAG, "Tethering " + iface); if (DBG) Log.d(TAG, "Tethering " + iface);
synchronized (mPublicSync) { synchronized (mPublicSync) {
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (sm == null) { if (tetherState == null) {
Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring"); Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
} }
// Ignore the error status of the interface. If the interface is available, // Ignore the error status of the interface. If the interface is available,
// the errors are referring to past tethering attempts anyway. // the errors are referring to past tethering attempts anyway.
if (!sm.isAvailable()) { if (tetherState.mLastState != IControlsTethering.STATE_AVAILABLE) {
Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
} }
sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
return ConnectivityManager.TETHER_ERROR_NO_ERROR; return ConnectivityManager.TETHER_ERROR_NO_ERROR;
} }
} }
@@ -602,43 +605,43 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
public int untether(String iface) { public int untether(String iface) {
if (DBG) Log.d(TAG, "Untethering " + iface); if (DBG) Log.d(TAG, "Untethering " + iface);
synchronized (mPublicSync) { synchronized (mPublicSync) {
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (sm == null) { if (tetherState == null) {
Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
} }
if (!sm.isTethered()) { if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
} }
sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); tetherState.mStateMachine.sendMessage(
TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
return ConnectivityManager.TETHER_ERROR_NO_ERROR; return ConnectivityManager.TETHER_ERROR_NO_ERROR;
} }
} }
public void untetherAll() { public void untetherAll() {
if (DBG) Log.d(TAG, "Untethering " + mIfaces); synchronized (mPublicSync) {
for (String iface : mIfaces.keySet()) { if (DBG) Log.d(TAG, "Untethering " + mTetherStates.keySet());
untether(iface); for (int i = 0; i < mTetherStates.size(); i++) {
untether(mTetherStates.keyAt(i));
}
} }
} }
public int getLastTetherError(String iface) { public int getLastTetherError(String iface) {
synchronized (mPublicSync) { synchronized (mPublicSync) {
TetherInterfaceStateMachine sm = mIfaces.get(iface); TetherState tetherState = mTetherStates.get(iface);
if (sm == null) { if (tetherState == null) {
Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface + Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
", ignoring"); ", ignoring");
return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
} }
return sm.getLastError(); return tetherState.mLastError;
} }
} }
// TODO - move all private methods used only by the state machine into the state machine private void sendTetherStateChangedBroadcast() {
// to clarify what needs synchronized protection.
@Override
public void sendTetherStateChangedBroadcast() {
if (!getConnectivityManager().isTetheringSupported()) return; if (!getConnectivityManager().isTetheringSupported()) return;
ArrayList<String> availableList = new ArrayList<String>(); ArrayList<String> availableList = new ArrayList<String>();
@@ -650,24 +653,22 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
boolean bluetoothTethered = false; boolean bluetoothTethered = false;
synchronized (mPublicSync) { synchronized (mPublicSync) {
Set<String> ifaces = mIfaces.keySet(); for (int i = 0; i < mTetherStates.size(); i++) {
for (String iface : ifaces) { TetherState tetherState = mTetherStates.valueAt(i);
TetherInterfaceStateMachine sm = mIfaces.get(iface); String iface = mTetherStates.keyAt(i);
if (sm != null) { if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
if (sm.isErrored()) { erroredList.add(iface);
erroredList.add(iface); } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
} else if (sm.isAvailable()) { availableList.add(iface);
availableList.add(iface); } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
} else if (sm.isTethered()) { if (isUsb(iface)) {
if (isUsb(iface)) { usbTethered = true;
usbTethered = true; } else if (isWifi(iface)) {
} else if (isWifi(iface)) { wifiTethered = true;
wifiTethered = true; } else if (isBluetooth(iface)) {
} else if (isBluetooth(iface)) { bluetoothTethered = true;
bluetoothTethered = true;
}
activeList.add(iface);
} }
activeList.add(iface);
} }
} }
} }
@@ -961,37 +962,27 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
public String[] getTetheredIfaces() { public String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>(); ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) { synchronized (mPublicSync) {
Set<String> keys = mIfaces.keySet(); for (int i = 0; i < mTetherStates.size(); i++) {
for (String key : keys) { TetherState tetherState = mTetherStates.valueAt(i);
TetherInterfaceStateMachine sm = mIfaces.get(key); if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
if (sm.isTethered()) { list.add(mTetherStates.keyAt(i));
list.add(key);
} }
} }
} }
String[] retVal = new String[list.size()]; return list.toArray(new String[list.size()]);
for (int i=0; i < list.size(); i++) {
retVal[i] = list.get(i);
}
return retVal;
} }
public String[] getTetherableIfaces() { public String[] getTetherableIfaces() {
ArrayList<String> list = new ArrayList<String>(); ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) { synchronized (mPublicSync) {
Set<String> keys = mIfaces.keySet(); for (int i = 0; i < mTetherStates.size(); i++) {
for (String key : keys) { TetherState tetherState = mTetherStates.valueAt(i);
TetherInterfaceStateMachine sm = mIfaces.get(key); if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
if (sm.isAvailable()) { list.add(mTetherStates.keyAt(i));
list.add(key);
} }
} }
} }
String[] retVal = new String[list.size()]; return list.toArray(new String[list.size()]);
for (int i=0; i < list.size(); i++) {
retVal[i] = list.get(i);
}
return retVal;
} }
public String[] getTetheredDhcpRanges() { public String[] getTetheredDhcpRanges() {
@@ -1001,19 +992,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
public String[] getErroredIfaces() { public String[] getErroredIfaces() {
ArrayList<String> list = new ArrayList<String>(); ArrayList<String> list = new ArrayList<String>();
synchronized (mPublicSync) { synchronized (mPublicSync) {
Set<String> keys = mIfaces.keySet(); for (int i = 0; i < mTetherStates.size(); i++) {
for (String key : keys) { TetherState tetherState = mTetherStates.valueAt(i);
TetherInterfaceStateMachine sm = mIfaces.get(key); if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
if (sm.isErrored()) { list.add(mTetherStates.keyAt(i));
list.add(key);
} }
} }
} }
String[] retVal = new String[list.size()]; return list.toArray(new String[list.size()]);
for (int i= 0; i< list.size(); i++) {
retVal[i] = list.get(i);
}
return retVal;
} }
private void maybeLogMessage(State state, int what) { private void maybeLogMessage(State state, int what) {
@@ -1143,6 +1129,18 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
private State mStopTetheringErrorState; private State mStopTetheringErrorState;
private State mSetDnsForwardersErrorState; private State mSetDnsForwardersErrorState;
// This list is a little subtle. It contains all the interfaces that currently are
// requesting tethering, regardless of whether these interfaces are still members of
// mTetherStates. This allows us to maintain the following predicates:
//
// 1) mTetherStates contains the set of all currently existing, tetherable, link state up
// interfaces.
// 2) mNotifyList contains all state machines that may have outstanding tethering state
// that needs to be torn down.
//
// Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
// so that the garbage collector does not clean up the state machine before it has a chance
// to tear itself down.
private ArrayList<TetherInterfaceStateMachine> mNotifyList; private ArrayList<TetherInterfaceStateMachine> mNotifyList;
private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
@@ -1453,15 +1451,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) { config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
ArrayList<Integer> tethered = new ArrayList<Integer>(); ArrayList<Integer> tethered = new ArrayList<Integer>();
synchronized (mPublicSync) { synchronized (mPublicSync) {
Set<String> ifaces = mIfaces.keySet(); for (int i = 0; i < mTetherStates.size(); i++) {
for (String iface : ifaces) { TetherState tetherState = mTetherStates.valueAt(i);
TetherInterfaceStateMachine sm = mIfaces.get(iface); if (tetherState.mLastState !=
if (sm != null && sm.isTethered()) { IControlsTethering.STATE_TETHERED) {
int interfaceType = ifaceNameToType(iface); continue; // Skip interfaces that aren't tethered.
if (interfaceType != }
ConnectivityManager.TETHERING_INVALID) { String iface = mTetherStates.keyAt(i);
tethered.add(new Integer(interfaceType)); int interfaceType = ifaceNameToType(iface);
} if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
tethered.add(new Integer(interfaceType));
} }
} }
} }
@@ -1487,9 +1486,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
} }
class InitialState extends TetherMasterUtilState { class InitialState extends TetherMasterUtilState {
@Override
public void enter() {
}
@Override @Override
public boolean processMessage(Message message) { public boolean processMessage(Message message) {
maybeLogMessage(this, message.what); maybeLogMessage(this, message.what);
@@ -1498,16 +1494,15 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
case CMD_TETHER_MODE_REQUESTED: case CMD_TETHER_MODE_REQUESTED:
TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
mNotifyList.add(who); if (mNotifyList.indexOf(who) < 0) {
mNotifyList.add(who);
}
transitionTo(mTetherModeAliveState); transitionTo(mTetherModeAliveState);
break; break;
case CMD_TETHER_MODE_UNREQUESTED: case CMD_TETHER_MODE_UNREQUESTED:
who = (TetherInterfaceStateMachine)message.obj; who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
int index = mNotifyList.indexOf(who); mNotifyList.remove(who);
if (index != -1) {
mNotifyList.remove(who);
}
break; break;
default: default:
retValue = false; retValue = false;
@@ -1546,24 +1541,26 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
case CMD_TETHER_MODE_REQUESTED: case CMD_TETHER_MODE_REQUESTED:
TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
mNotifyList.add(who); if (mNotifyList.indexOf(who) < 0) {
mNotifyList.add(who);
}
who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
mCurrentUpstreamIface); mCurrentUpstreamIface);
break; break;
case CMD_TETHER_MODE_UNREQUESTED: case CMD_TETHER_MODE_UNREQUESTED:
who = (TetherInterfaceStateMachine)message.obj; who = (TetherInterfaceStateMachine)message.obj;
if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
int index = mNotifyList.indexOf(who); if (mNotifyList.remove(who)) {
if (index != -1) {
if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who); if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
mNotifyList.remove(index);
if (mNotifyList.isEmpty()) { if (mNotifyList.isEmpty()) {
turnOffMasterTetherSettings(); // transitions appropriately turnOffMasterTetherSettings(); // transitions appropriately
} else { } else {
if (DBG) { if (DBG) {
Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() + Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
" live requests:"); " live requests:");
for (Object o : mNotifyList) Log.d(TAG, " " + o); for (TetherInterfaceStateMachine o : mNotifyList) {
Log.d(TAG, " " + o);
}
} }
} }
} else { } else {
@@ -1623,8 +1620,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
} }
void notify(int msgType) { void notify(int msgType) {
mErrorNotification = msgType; mErrorNotification = msgType;
for (Object o : mNotifyList) { for (TetherInterfaceStateMachine sm : mNotifyList) {
TetherInterfaceStateMachine sm = (TetherInterfaceStateMachine)o;
sm.sendMessage(msgType); sm.sendMessage(msgType);
} }
} }
@@ -1707,8 +1703,26 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
pw.println("Tether state:"); pw.println("Tether state:");
pw.increaseIndent(); pw.increaseIndent();
for (Object o : mIfaces.values()) { for (int i = 0; i < mTetherStates.size(); i++) {
pw.println(o); final String iface = mTetherStates.keyAt(i);
final TetherState tetherState = mTetherStates.valueAt(i);
pw.print(iface + " - ");
switch (tetherState.mLastState) {
case IControlsTethering.STATE_UNAVAILABLE:
pw.print("UnavailableState");
break;
case IControlsTethering.STATE_AVAILABLE:
pw.print("AvailableState");
break;
case IControlsTethering.STATE_TETHERED:
pw.print("TetheredState");
break;
default:
pw.print("UnknownState");
break;
}
pw.println(" - lastError = " + tetherState.mLastError);
} }
pw.decreaseIndent(); pw.decreaseIndent();
} }
@@ -1716,9 +1730,40 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
} }
@Override @Override
public void notifyInterfaceTetheringReadiness(boolean isReady, public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
TetherInterfaceStateMachine who) { int state, int error) {
mTetherMasterSM.sendMessage((isReady) ? TetherMasterSM.CMD_TETHER_MODE_REQUESTED synchronized (mPublicSync) {
: TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who); TetherState tetherState = mTetherStates.get(iface);
if (tetherState != null && tetherState.mStateMachine.equals(who)) {
tetherState.mLastState = state;
tetherState.mLastError = error;
} else {
if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
}
}
if (DBG) {
Log.d(TAG, "iface " + iface + " notified that it was in state " + state +
" with error " + error);
}
switch (state) {
case IControlsTethering.STATE_UNAVAILABLE:
case IControlsTethering.STATE_AVAILABLE:
mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
break;
case IControlsTethering.STATE_TETHERED:
mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
break;
}
sendTetherStateChangedBroadcast();
}
private void trackNewTetherableInterface(String iface, int interfaceType) {
TetherState tetherState;
tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
interfaceType, mNMService, mStatsService, this));
mTetherStates.put(iface, tetherState);
tetherState.mStateMachine.start();
} }
} }

View File

@@ -22,6 +22,18 @@ package com.android.server.connectivity.tethering;
* Interface with methods necessary to notify that a given interface is ready for tethering. * Interface with methods necessary to notify that a given interface is ready for tethering.
*/ */
public interface IControlsTethering { public interface IControlsTethering {
void sendTetherStateChangedBroadcast(); public final int STATE_UNAVAILABLE = 0;
void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceStateMachine who); public final int STATE_AVAILABLE = 1;
public final int STATE_TETHERED = 2;
/**
* Notify that |who| has changed its tethering state. This may be called from any thread.
*
* @param iface a network interface (e.g. "wlan0")
* @param who corresponding instance of a TetherInterfaceStateMachine
* @param state one of IControlsTethering.STATE_*
* @param lastError one of ConnectivityManager.TETHER_ERROR_*
*/
void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
int state, int lastError);
} }

View File

@@ -27,7 +27,6 @@ import android.os.Message;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.util.IState;
import com.android.internal.util.MessageUtils; import com.android.internal.util.MessageUtils;
import com.android.internal.util.Protocol; import com.android.internal.util.Protocol;
import com.android.internal.util.State; import com.android.internal.util.State;
@@ -98,7 +97,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
mTetherController = tetherController; mTetherController = tetherController;
mIfaceName = ifaceName; mIfaceName = ifaceName;
mInterfaceType = interfaceType; mInterfaceType = interfaceType;
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mInitialState = new InitialState(); mInitialState = new InitialState();
addState(mInitialState); addState(mInitialState);
@@ -110,40 +109,6 @@ public class TetherInterfaceStateMachine extends StateMachine {
setInitialState(mInitialState); 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 // configured when we start tethering and unconfig'd on error or conclusion
private boolean configureIfaceIp(boolean enabled) { private boolean configureIfaceIp(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")"); if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
@@ -193,7 +158,9 @@ public class TetherInterfaceStateMachine extends StateMachine {
class InitialState extends State { class InitialState extends State {
@Override @Override
public void enter() { public void enter() {
mTetherController.sendTetherStateChangedBroadcast(); mTetherController.notifyInterfaceStateChange(
mIfaceName, TetherInterfaceStateMachine.this,
IControlsTethering.STATE_AVAILABLE, mLastError);
} }
@Override @Override
@@ -202,8 +169,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
boolean retValue = true; boolean retValue = true;
switch (message.what) { switch (message.what) {
case CMD_TETHER_REQUESTED: case CMD_TETHER_REQUESTED:
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceStateMachine.this);
transitionTo(mTetheredState); transitionTo(mTetheredState);
break; break;
case CMD_INTERFACE_DOWN: case CMD_INTERFACE_DOWN:
@@ -221,7 +187,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
@Override @Override
public void enter() { public void enter() {
if (!configureIfaceIp(true)) { if (!configureIfaceIp(true)) {
setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
transitionTo(mInitialState); transitionTo(mInitialState);
return; return;
} }
@@ -230,19 +196,18 @@ public class TetherInterfaceStateMachine extends StateMachine {
mNMService.tetherInterface(mIfaceName); mNMService.tetherInterface(mIfaceName);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error Tethering: " + e.toString()); Log.e(TAG, "Error Tethering: " + e.toString());
setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
transitionTo(mInitialState); transitionTo(mInitialState);
return; return;
} }
if (DBG) Log.d(TAG, "Tethered " + mIfaceName); if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
mTetherController.sendTetherStateChangedBroadcast(); mTetherController.notifyInterfaceStateChange(
mIfaceName, TetherInterfaceStateMachine.this,
IControlsTethering.STATE_TETHERED, mLastError);
} }
@Override @Override
public void exit() { public void exit() {
mTetherController.notifyInterfaceTetheringReadiness(false,
TetherInterfaceStateMachine.this);
// Note that at this point, we're leaving the tethered state. We can fail any // 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 // of these operations, but it doesn't really change that we have to try them
// all in sequence. // all in sequence.
@@ -251,7 +216,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
try { try {
mNMService.untetherInterface(mIfaceName); mNMService.untetherInterface(mIfaceName);
} catch (Exception ee) { } catch (Exception ee) {
setLastError(ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
Log.e(TAG, "Failed to untether interface: " + ee.toString()); Log.e(TAG, "Failed to untether interface: " + ee.toString());
} }
@@ -315,7 +280,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
newUpstreamIfaceName); newUpstreamIfaceName);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Exception enabling Nat: " + e.toString()); Log.e(TAG, "Exception enabling Nat: " + e.toString());
setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
transitionTo(mInitialState); transitionTo(mInitialState);
return true; return true;
} }
@@ -327,8 +292,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
case CMD_START_TETHERING_ERROR: case CMD_START_TETHERING_ERROR:
case CMD_STOP_TETHERING_ERROR: case CMD_STOP_TETHERING_ERROR:
case CMD_SET_DNS_FORWARDERS_ERROR: case CMD_SET_DNS_FORWARDERS_ERROR:
setLastErrorAndTransitionToInitialState( mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
ConnectivityManager.TETHER_ERROR_MASTER_ERROR); transitionTo(mInitialState);
break; break;
default: default:
retValue = false; retValue = false;
@@ -348,13 +313,10 @@ public class TetherInterfaceStateMachine extends StateMachine {
class UnavailableState extends State { class UnavailableState extends State {
@Override @Override
public void enter() { public void enter() {
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mTetherController.sendTetherStateChangedBroadcast(); mTetherController.notifyInterfaceStateChange(
mIfaceName, TetherInterfaceStateMachine.this,
IControlsTethering.STATE_UNAVAILABLE, mLastError);
} }
} }
void setLastErrorAndTransitionToInitialState(int error) {
setLastError(error);
transitionTo(mInitialState);
}
} }

View File

@@ -16,8 +16,6 @@
package com.android.server.connectivity.tethering; 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.Matchers.anyString;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.inOrder;
@@ -26,6 +24,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.INetworkStatsService; import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration; import android.net.InterfaceConfiguration;
@@ -78,8 +84,7 @@ public class TetherInterfaceStateMachineTest {
when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
} }
@Before @Before public void setUp() throws Exception {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
} }
@@ -89,10 +94,8 @@ public class TetherInterfaceStateMachineTest {
ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper); ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
mTestedSm.start(); mTestedSm.start();
mLooper.dispatchAll(); mLooper.dispatchAll();
assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); verify(mTetherHelper).notifyInterfaceStateChange(
assertFalse("Should not be tethered initially", mTestedSm.isTethered()); IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
assertFalse("Should have no errors initially", mTestedSm.isErrored());
verify(mTetherHelper).sendTetherStateChangedBroadcast();
verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
} }
@@ -119,28 +122,23 @@ public class TetherInterfaceStateMachineTest {
@Test @Test
public void handlesImmediateInterfaceDown() throws Exception { public void handlesImmediateInterfaceDown() throws Exception {
initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
verify(mTetherHelper).sendTetherStateChangedBroadcast(); verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
public void canBeTethered() throws Exception { public void canBeTethered() throws Exception {
initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
InOrder inOrder = inOrder(mTetherHelper, mNMService); InOrder inOrder = inOrder(mTetherHelper, mNMService);
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
inOrder.verify(mNMService).tetherInterface(IFACE_NAME); inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -149,13 +147,10 @@ public class TetherInterfaceStateMachineTest {
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME); inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -164,16 +159,12 @@ public class TetherInterfaceStateMachineTest {
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
InOrder inOrder = inOrder(mTetherHelper, mNMService); InOrder inOrder = inOrder(mTetherHelper, mNMService);
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
inOrder.verify(mNMService).tetherInterface(IFACE_NAME); inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -186,9 +177,6 @@ public class TetherInterfaceStateMachineTest {
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -203,9 +191,6 @@ public class TetherInterfaceStateMachineTest {
inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -214,16 +199,13 @@ public class TetherInterfaceStateMachineTest {
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
inOrder.verify(mStatsService).forceUpdate(); inOrder.verify(mStatsService).forceUpdate();
inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
inOrder.verify(mNMService).untetherInterface(IFACE_NAME); inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 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 @Test
@@ -235,13 +217,12 @@ public class TetherInterfaceStateMachineTest {
doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
} }
dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig( usbTeardownOrder.verify(mNMService).setInterfaceConfig(
IFACE_NAME, mInterfaceConfiguration); IFACE_NAME, mInterfaceConfiguration);
verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
assertFalse("Should not be available", mTestedSm.isAvailable()); IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
assertFalse("Should not be tethered", mTestedSm.isTethered());
} }
} }
@@ -251,15 +232,12 @@ public class TetherInterfaceStateMachineTest {
doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig( usbTeardownOrder.verify(mNMService).setInterfaceConfig(
IFACE_NAME, mInterfaceConfiguration); IFACE_NAME, mInterfaceConfiguration);
// Initial call is when we transition to the tethered state on request. usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
// 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 @Test
@@ -268,10 +246,11 @@ public class TetherInterfaceStateMachineTest {
doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
dispatchTetherConnectionChanged(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE);
InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
} }
/** /**