Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1315c3c authored by Christopher Wiley's avatar Christopher Wiley
Browse files

Tethering: Own WiFi tethering state and lifetime

- Add logic to Tethering to track whether the user has requested
  tethering via WiFi.
- Subscribe to intents regarding soft AP state to enable and
  disable tethering when the AP comes up or goes down.
- Refactor IP configuration logic to do configuration for WiFi
  as well as USB.

Bug: 29054780
Test: WiFi tethering continues to work on angler
      Tethering related unittests continue to pass.

Change-Id: I6eff2573ca3fd11fabcf138c468ba517ff2daf65
parent 9d5c0b1c
Loading
Loading
Loading
Loading
+105 −63
Original line number Diff line number Diff line
@@ -128,7 +128,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering

    private Map<String, TetherInterfaceStateMachine> mIfaces; // all tethered/tetherable ifaces

    private BroadcastReceiver mStateReceiver;
    private final BroadcastReceiver mStateReceiver;

    // {@link ComponentName} of the Service used to run tether provisioning.
    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
@@ -163,6 +163,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
    private boolean mUsbTetherRequested; // true if USB tethering should be started
                                         // when RNDIS is enabled

    // True iff WiFi tethering should be started when soft AP is ready.
    private boolean mWifiTetherRequested;

    public Tethering(Context context, INetworkManagementService nmService,
            INetworkStatsService statsService) {
        mContext = context;
@@ -184,6 +187,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        IntentFilter filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_STATE);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
        mContext.registerReceiver(mStateReceiver, filter);

@@ -245,29 +249,22 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        // Never called directly: only called from interfaceLinkStateChanged.
        // See NetlinkHandler.cpp:71.
        if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
        boolean found = false;
        boolean usb = false;
        synchronized (mPublicSync) {
            if (isWifi(iface)) {
                found = true;
            } else if (isUsb(iface)) {
                found = true;
                usb = true;
            } else if (isBluetooth(iface)) {
                found = true;
            int interfaceType = ifaceNameToType(iface);
            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
                return;
            }
            if (found == false) return;

            TetherInterfaceStateMachine sm = mIfaces.get(iface);
            if (up) {
                if (sm == null) {
                    sm = new TetherInterfaceStateMachine(iface, mLooper, usb,
                    sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType,
                            mNMService, mStatsService, this);
                    mIfaces.put(iface, sm);
                    sm.start();
                }
            } else {
                if (isUsb(iface)) {
                if (interfaceType == ConnectivityManager.TETHERING_USB) {
                    // ignore usb0 down after enabling RNDIS
                    // we will handle disconnect in interfaceRemoved instead
                    if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
@@ -293,7 +290,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    public boolean isWifi(String iface) {
    private boolean isWifi(String iface) {
        synchronized (mPublicSync) {
            for (String regex : mTetherableWifiRegexs) {
                if (iface.matches(regex)) return true;
@@ -302,7 +299,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    public boolean isBluetooth(String iface) {
    private boolean isBluetooth(String iface) {
        synchronized (mPublicSync) {
            for (String regex : mTetherableBluetoothRegexs) {
                if (iface.matches(regex)) return true;
@@ -311,23 +308,23 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    private int ifaceNameToType(String iface) {
        if (isWifi(iface)) {
            return ConnectivityManager.TETHERING_WIFI;
        } else if (isUsb(iface)) {
            return ConnectivityManager.TETHERING_USB;
        } else if (isBluetooth(iface)) {
            return ConnectivityManager.TETHERING_BLUETOOTH;
        }
        return ConnectivityManager.TETHERING_INVALID;
    }

    @Override
    public void interfaceAdded(String iface) {
        if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
        boolean found = false;
        boolean usb = false;
        synchronized (mPublicSync) {
            if (isWifi(iface)) {
                found = true;
            }
            if (isUsb(iface)) {
                found = true;
                usb = true;
            }
            if (isBluetooth(iface)) {
                found = true;
            }
            if (found == false) {
            int interfaceType = ifaceNameToType(iface);
            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
                if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
                return;
            }
@@ -337,7 +334,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
                return;
            }
            sm = new TetherInterfaceStateMachine(iface, mLooper, usb,
            sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType,
                    mNMService, mStatsService, this);
            mIfaces.put(iface, sm);
            sm.start();
@@ -412,24 +409,19 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
     * for the specified interface.
     */
    private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
        boolean isProvisioningRequired = isTetherProvisioningRequired();
        boolean isProvisioningRequired = enable && isTetherProvisioningRequired();
        int result;
        switch (type) {
            case ConnectivityManager.TETHERING_WIFI:
                final WifiManager wifiManager =
                        (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
                if (wifiManager.setWifiApEnabled(null, enable)) {
                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR);
                    if (enable && isProvisioningRequired) {
                result = setWifiTethering(enable);
                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                    scheduleProvisioningRechecks(type);
                }
                } else{
                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
                }
                sendTetherResult(receiver, result);
                break;
            case ConnectivityManager.TETHERING_USB:
                int result = setUsbTethering(enable);
                if (enable && isProvisioningRequired &&
                        result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                result = setUsbTethering(enable);
                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                    scheduleProvisioningRechecks(type);
                }
                sendTetherResult(receiver, result);
@@ -449,6 +441,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    private int setWifiTethering(final boolean enable) {
        synchronized (mPublicSync) {
            // Note that we're maintaining a predicate that mWifiTetherRequested always matches
            // our last request to WifiManager re: its AP enabled status.
            mWifiTetherRequested = enable;
            final WifiManager wifiManager =
                    (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
            if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) {
                return ConnectivityManager.TETHER_ERROR_NO_ERROR;
            }
            return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
        }
    }

    private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null || !adapter.isEnabled()) {
@@ -770,7 +776,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                    mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
                    // start tethering if we have a request pending
                    if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
                        tetherUsb(true);
                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                    }
                    mUsbTetherRequested = false;
                }
@@ -782,31 +788,72 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                    if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
                    mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
                }
            } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                synchronized (Tethering.this.mPublicSync) {
                    if (!mWifiTetherRequested) {
                        // We only care when we're trying to tether via our WiFi interface.
                        return;
                    }
                    int curState =  intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
                            WifiManager.WIFI_AP_STATE_DISABLED);
                    switch (curState) {
                        case WifiManager.WIFI_AP_STATE_ENABLING:
                            // We can see this state on the way to both enabled and failure states.
                            break;
                        case WifiManager.WIFI_AP_STATE_ENABLED:
                            // Tell an appropriate interface state machine that it should tether.
                            tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
                            break;
                        case WifiManager.WIFI_AP_STATE_DISABLED:
                        case WifiManager.WIFI_AP_STATE_DISABLING:
                        case WifiManager.WIFI_AP_STATE_FAILED:
                        default:
                            if (DBG) {
                                Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
                                    curState);
                            }
                            // Tell an appropriate interface state machine that
                            // it needs to tear itself down.
                            tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_WIFI);
                            setWifiTethering(false);
                            break;
                    }
                }
            } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                updateConfiguration();
            }
        }
    }

    private void tetherUsb(boolean enable) {
        if (VDBG) Log.d(TAG, "tetherUsb " + enable);
    private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
        if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");

        String[] ifaces = new String[0];
        String[] ifaces = null;
        try {
            ifaces = mNMService.listInterfaces();
        } catch (Exception e) {
            Log.e(TAG, "Error listing Interfaces", e);
            return;
        }
        String chosenIface = null;
        if (ifaces != null) {
            for (String iface : ifaces) {
            if (isUsb(iface)) {
                int result = (enable ? tether(iface) : untether(iface));
                if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                    return;
                if (ifaceNameToType(iface) == interfaceType) {
                    chosenIface = iface;
                    break;
                }
            }
        }
        Log.e(TAG, "unable start or stop USB tethering");
        if (chosenIface == null) {
            Log.e(TAG, "could not find iface of type " + interfaceType);
            return;
        }

        int result = (enable ? tether(chosenIface) : untether(chosenIface));
        if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
            Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
            return;
        }
    }

    // TODO - return copies so people can't tamper
@@ -831,7 +878,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                if (mRndisEnabled) {
                    final long ident = Binder.clearCallingIdentity();
                    try {
                        tetherUsb(true);
                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
@@ -842,7 +889,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            } else {
                final long ident = Binder.clearCallingIdentity();
                try {
                    tetherUsb(false);
                    tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
@@ -1410,15 +1457,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                                for (String iface : ifaces) {
                                    TetherInterfaceStateMachine sm = mIfaces.get(iface);
                                    if (sm != null && sm.isTethered()) {
                                        if (isUsb(iface)) {
                                            tethered.add(new Integer(
                                                    ConnectivityManager.TETHERING_USB));
                                        } else if (isWifi(iface)) {
                                            tethered.add(new Integer(
                                                    ConnectivityManager.TETHERING_WIFI));
                                        } else if (isBluetooth(iface)) {
                                            tethered.add(new Integer(
                                                    ConnectivityManager.TETHERING_BLUETOOTH));
                                        int interfaceType = ifaceNameToType(iface);
                                        if (interfaceType !=
                                                ConnectivityManager.TETHERING_INVALID) {
                                            tethered.add(new Integer(interfaceType));
                                        }
                                    }
                                }
+30 −19
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import java.net.InetAddress;
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;
@@ -81,13 +83,13 @@ public class TetherInterfaceStateMachine extends StateMachine {
    private final INetworkStatsService mStatsService;
    private final IControlsTethering mTetherController;

    private final boolean mUsb;
    private final String mIfaceName;
    private final int mInterfaceType;

    private int mLastError;
    private String mMyUpstreamIfaceName;  // may change over time

    public TetherInterfaceStateMachine(String ifaceName, Looper looper, boolean usb,
    public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
                    INetworkManagementService nMService, INetworkStatsService statsService,
                    IControlsTethering tetherController) {
        super(ifaceName, looper);
@@ -95,7 +97,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
        mStatsService = statsService;
        mTetherController = tetherController;
        mIfaceName = ifaceName;
        mUsb = usb;
        mInterfaceType = interfaceType;
        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);

        mInitialState = new InitialState();
@@ -143,25 +145,38 @@ public class TetherInterfaceStateMachine extends StateMachine {
    }

    // configured when we start tethering and unconfig'd on error or conclusion
    private boolean configureUsbIface(boolean enabled, String iface) {
        if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
    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(iface);
            ifcg = mNMService.getInterfaceConfig(mIfaceName);
            if (ifcg != null) {
                InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
                ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
                InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
                ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
                if (enabled) {
                    ifcg.setInterfaceUp();
                } else {
                    ifcg.setInterfaceDown();
                }
                ifcg.clearFlag("running");
                mNMService.setInterfaceConfig(iface, ifcg);
                mNMService.setInterfaceConfig(mIfaceName, ifcg);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error configuring interface " + iface, e);
            Log.e(TAG, "Error configuring interface " + mIfaceName, e);
            return false;
        }

@@ -205,13 +220,11 @@ public class TetherInterfaceStateMachine extends StateMachine {
    class TetheredState extends State {
        @Override
        public void enter() {
            if (mUsb) {
                if (!configureUsbIface(true, mIfaceName)) {
            if (!configureIfaceIp(true)) {
                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
                transitionTo(mInitialState);
                return;
            }
            }

            try {
                mNMService.tetherInterface(mIfaceName);
@@ -242,9 +255,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
                Log.e(TAG, "Failed to untether interface: " + ee.toString());
            }

            if (mUsb) {
                configureUsbIface(false, mIfaceName);
            }
            configureIfaceIp(false);
        }

        private void cleanupUpstream() {
+18 −17
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ 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;
@@ -56,8 +57,8 @@ public class TetherInterfaceStateMachineTest {
    private final TestLooper mLooper = new TestLooper();
    private TetherInterfaceStateMachine mTestedSm;

    private void initStateMachine(boolean isUsb) throws Exception {
        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb,
    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
@@ -67,8 +68,8 @@ public class TetherInterfaceStateMachineTest {
        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
    }

    private void initTetheredStateMachine(boolean isUsb, String upstreamIface) throws Exception {
        initStateMachine(isUsb);
    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
        initStateMachine(interfaceType);
        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
        if (upstreamIface != null) {
            dispatchTetherConnectionChanged(upstreamIface);
@@ -84,8 +85,8 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void startsOutAvailable() {
        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false,
                mNMService, mStatsService, mTetherHelper);
        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());
@@ -97,7 +98,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void shouldDoNothingUntilRequested() throws Exception {
        initStateMachine(false);
        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
        final int [] NOOP_COMMANDS = {
            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
            TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
@@ -117,7 +118,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void handlesImmediateInterfaceDown() throws Exception {
        initStateMachine(false);
        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
        dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
        verify(mTetherHelper).sendTetherStateChangedBroadcast();
        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
@@ -129,7 +130,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void canBeTethered() throws Exception {
        initStateMachine(false);
        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
        InOrder inOrder = inOrder(mTetherHelper, mNMService);
        inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
@@ -144,7 +145,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void canUnrequestTethering() throws Exception {
        initTetheredStateMachine(false, null);
        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);

        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
@@ -159,7 +160,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void canBeTetheredAsUsb() throws Exception {
        initStateMachine(true);
        initStateMachine(ConnectivityManager.TETHERING_USB);

        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
        InOrder inOrder = inOrder(mTetherHelper, mNMService);
@@ -177,7 +178,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void handlesFirstUpstreamChange() throws Exception {
        initTetheredStateMachine(false, null);
        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);

        // Telling the state machine about its upstream interface triggers a little more configuration.
        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
@@ -192,7 +193,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void handlesChangingUpstream() throws Exception {
        initTetheredStateMachine(false, UPSTREAM_IFACE);
        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);

        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
        InOrder inOrder = inOrder(mNMService, mStatsService);
@@ -209,7 +210,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void canUnrequestTetheringWithUpstream() throws Exception {
        initTetheredStateMachine(false, UPSTREAM_IFACE);
        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);

        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
@@ -228,7 +229,7 @@ public class TetherInterfaceStateMachineTest {
    @Test
    public void interfaceDownLeadsToUnavailable() throws Exception {
        for (boolean shouldThrow : new boolean[]{true, false}) {
            initTetheredStateMachine(true, null);
            initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);

            if (shouldThrow) {
                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
@@ -246,7 +247,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void usbShouldBeTornDownOnTetherError() throws Exception {
        initStateMachine(true);
        initStateMachine(ConnectivityManager.TETHERING_USB);

        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
@@ -263,7 +264,7 @@ public class TetherInterfaceStateMachineTest {

    @Test
    public void shouldTearDownUsbOnUpstreamError() throws Exception {
        initTetheredStateMachine(true, null);
        initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);

        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
        dispatchTetherConnectionChanged(UPSTREAM_IFACE);