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
This commit is contained in:
Christopher Wiley
2016-05-31 14:43:08 -07:00
parent 9d5c0b1c8e
commit f1315c3cd6
3 changed files with 153 additions and 99 deletions

View File

@@ -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) {
scheduleProvisioningRechecks(type);
}
} else{
sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
result = setWifiTethering(enable);
if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
scheduleProvisioningRechecks(type);
}
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;
}
for (String iface : ifaces) {
if (isUsb(iface)) {
int result = (enable ? tether(iface) : untether(iface));
if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
return;
String chosenIface = null;
if (ifaces != null) {
for (String iface : ifaces) {
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));
}
}
}

View File

@@ -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,12 +220,10 @@ public class TetherInterfaceStateMachine extends StateMachine {
class TetheredState extends State {
@Override
public void enter() {
if (mUsb) {
if (!configureUsbIface(true, mIfaceName)) {
setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
transitionTo(mInitialState);
return;
}
if (!configureIfaceIp(true)) {
setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
transitionTo(mInitialState);
return;
}
try {
@@ -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() {

View File

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