Tethering: Own WiFi tethering state and lifetime
( cherry-pick of f1315c3cd6d77e812ae32fe038b4e8bf2e70d5bf )
- 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
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 40d17ed..4a0e81b 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -128,7 +128,7 @@
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 @@
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 @@
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 @@
// 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 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 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 @@
}
}
+ 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 @@
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 @@
* 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 @@
}
}
+ 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 @@
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 @@
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 @@
if (mRndisEnabled) {
final long ident = Binder.clearCallingIdentity();
try {
- tetherUsb(true);
+ tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -842,7 +889,7 @@
} else {
final long ident = Binder.clearCallingIdentity();
try {
- tetherUsb(false);
+ tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1410,15 +1457,10 @@
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));
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index e33406e6..b8bea60 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -43,6 +43,8 @@
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 @@
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 @@
mStatsService = statsService;
mTetherController = tetherController;
mIfaceName = ifaceName;
- mUsb = usb;
+ mInterfaceType = interfaceType;
setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
mInitialState = new InitialState();
@@ -143,25 +145,38 @@
}
// 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 @@
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 @@
Log.e(TAG, "Failed to untether interface: " + ee.toString());
}
- if (mUsb) {
- configureUsbIface(false, mIfaceName);
- }
+ configureIfaceIp(false);
}
private void cleanupUpstream() {
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
index 4410846..30a7dbc 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -26,6 +26,7 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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);