Disallow legacy tether() API starting in B
As part of the major tethering refactor to associate every IpServer with
a TetheringRequest, tether()/untether() APIs should be completely
removed in B and all tethering should go through startTethering() (with
the exception of Wifi LOHS and Wifi P2P, which will move to
startTethering() in a future CL).
Bug: 216524590
Test: atest CtsTetheringTests TetheringTests
Change-Id: If7981f1ee7d80ff0a1cc0069ba7a4ebf79144160
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index e81cbf0..21f36e8 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.IBinder;
@@ -657,6 +658,13 @@
}
}
+ private void unsupportedAfterV() {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ throw new UnsupportedOperationException("Not supported after SDK version "
+ + Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ }
+ }
+
/**
* Attempt to tether the named interface. This will setup a dhcp server
* on the interface, forward and NAT IP v4 packets and forward DNS requests
@@ -666,8 +674,10 @@
* access will of course fail until an upstream network interface becomes
* active.
*
- * @deprecated The only usages is PanService. It uses this for legacy reasons
- * and will migrate away as soon as possible.
+ * @deprecated Legacy tethering API. Callers should instead use
+ * {@link #startTethering(int, Executor, StartTetheringCallback)}.
+ * On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will
+ * throw an UnsupportedOperationException.
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -677,6 +687,8 @@
@Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public int tether(@NonNull final String iface) {
+ unsupportedAfterV();
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "tether caller:" + callerPkg);
final RequestDispatcher dispatcher = new RequestDispatcher();
@@ -700,14 +712,18 @@
/**
* Stop tethering the named interface.
*
- * @deprecated The only usages is PanService. It uses this for legacy reasons
- * and will migrate away as soon as possible.
+ * @deprecated Legacy tethering API. Callers should instead use
+ * {@link #stopTethering(int)}.
+ * On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will
+ * throw an UnsupportedOperationException.
*
* {@hide}
*/
@Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public int untether(@NonNull final String iface) {
+ unsupportedAfterV();
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "untether caller:" + callerPkg);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 4f07f58..40b1ec0 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -108,6 +108,7 @@
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -600,13 +601,40 @@
// This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
// can't use enableIpServing.
private void processInterfaceStateChange(final String iface, boolean enabled) {
+ final int type = ifaceNameToType(iface);
// Do not listen to USB interface state changes or USB interface add/removes. USB tethering
// is driven only by USB_ACTION broadcasts.
- final int type = ifaceNameToType(iface);
if (type == TETHERING_USB || type == TETHERING_NCM) return;
+ // On T+, BLUETOOTH uses enableIpServing.
if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
+ // Cannot happen: on S+, tetherableWigigRegexps is always empty.
+ if (type == TETHERING_WIGIG && SdkLevel.isAtLeastS()) return;
+
+ // After V, disallow this legacy codepath from starting tethering of any type:
+ // everything must call ensureIpServerStarted directly.
+ //
+ // Don't touch the teardown path for now. It's more complicated because:
+ // - ensureIpServerStarted and ensureIpServerStopped act on different
+ // tethering types.
+ // - Depending on the type, ensureIpServerStopped is either called twice (once
+ // on interface down and once on interface removed) or just once (on
+ // interface removed).
+ //
+ // Note that this only affects WIFI and WIFI_P2P. The other types are either
+ // ignored above, or ignored by ensureIpServerStarted. Note that even for WIFI
+ // and WIFI_P2P, this code should not ever run in normal use, because the
+ // hotspot and p2p code do not call tether(). It's possible that this could
+ // happen in the field due to unforeseen OEM modifications. If it does happen,
+ // a terrible error is logged in tether().
+ // TODO: fix the teardown path to stop depending on interface state notifications.
+ // These are not necessary since most/all link layers have their own teardown
+ // notifications, and can race with those notifications.
+ if (enabled && Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ return;
+ }
+
if (enabled) {
ensureIpServerStarted(iface);
} else {
@@ -999,7 +1027,27 @@
return TETHER_ERROR_NO_ERROR;
}
+ /**
+ * Legacy tether API that starts tethering with CONNECTIVITY_SCOPE_GLOBAL on the given iface.
+ *
+ * This API relies on the IpServer having been started for the interface by
+ * processInterfaceStateChanged beforehand, which is only possible for
+ * - WIGIG Pre-S
+ * - BLUETOOTH Pre-T
+ * - WIFI
+ * - WIFI_P2P.
+ * Note that WIFI and WIFI_P2P already start tethering on their respective ifaces via
+ * WIFI_(AP/P2P_STATE_CHANGED broadcasts, which makes this API redundant for those types unless
+ * those broadcasts are disabled by OEM.
+ */
void tether(String iface, int requestedState, final IIntResultListener listener) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ // After V, the TetheringManager and ConnectivityManager tether and untether methods
+ // throw UnsupportedOperationException, so this cannot happen in normal use. Ensure
+ // that this code cannot run even if callers use raw binder calls or other
+ // unsupported methods.
+ return;
+ }
mHandler.post(() -> {
switch (ifaceNameToType(iface)) {
case TETHERING_WIFI:
@@ -1051,6 +1099,13 @@
}
void untether(String iface, final IIntResultListener listener) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ // After V, the TetheringManager and ConnectivityManager tether and untether methods
+ // throw UnsupportedOperationException, so this cannot happen in normal use. Ensure
+ // that this code cannot run even if callers use raw binder calls or other
+ // unsupported methods.
+ return;
+ }
mHandler.post(() -> {
try {
listener.onResult(untether(iface));
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 9016d13..5d99b74 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -3065,7 +3065,8 @@
* <p>WARNING: New clients should not use this function. The only usages should be in PanService
* and WifiStateMachine which need direct access. All other clients should use
* {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
- * logic.</p>
+ * logic. On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will throw
+ * an UnsupportedOperationException.</p>
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
@@ -3090,7 +3091,8 @@
* <p>WARNING: New clients should not use this function. The only usages should be in PanService
* and WifiStateMachine which need direct access. All other clients should use
* {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
- * logic.</p>
+ * logic. On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will throw
+ * an UnsupportedOperationException.</p>
*
* @param iface the interface name to untether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 5e94c06..2420026 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -387,18 +388,21 @@
mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
- try {
- final int ret = runAsShell(TETHER_PRIVILEGED, () -> mTM.tether(wifiTetheringIface));
- // There is no guarantee that the wifi interface will be available after disabling
- // the hotspot, so don't fail the test if the call to tether() fails.
- if (ret == TETHER_ERROR_NO_ERROR) {
- // If calling #tether successful, there is a callback to tell the result of
- // tethering setup.
- tetherEventCallback.expectErrorOrTethered(
- new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ try {
+ final int ret = runAsShell(TETHER_PRIVILEGED,
+ () -> mTM.tether(wifiTetheringIface));
+ // There is no guarantee that the wifi interface will be available after
+ // disabling the hotspot, so don't fail the test if the call to tether() fails.
+ if (ret == TETHER_ERROR_NO_ERROR) {
+ // If calling #tether successful, there is a callback to tell the result of
+ // tethering setup.
+ tetherEventCallback.expectErrorOrTethered(
+ new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
+ }
+ } finally {
+ runAsShell(TETHER_PRIVILEGED, () -> mTM.untether(wifiTetheringIface));
}
- } finally {
- runAsShell(TETHER_PRIVILEGED, () -> mTM.untether(wifiTetheringIface));
}
} finally {
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
@@ -623,4 +627,11 @@
}
}
}
+
+ @Test
+ public void testLegacyTetherApisThrowUnsupportedOperationExceptionAfterV() {
+ assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ assertThrows(UnsupportedOperationException.class, () -> mTM.tether("iface"));
+ assertThrows(UnsupportedOperationException.class, () -> mTM.untether("iface"));
+ }
}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index 9a77c89..b415382 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -44,6 +44,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
@@ -65,6 +66,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ConnectivityManager.NetworkCallback;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Handler;
@@ -669,4 +671,12 @@
// No callbacks overridden -> do not use the optimization
eq(~0));
}
+
+ @Test
+ public void testLegacyTetherApisThrowUnsupportedOperationExceptionAfterV() {
+ assumeTrue(Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM);
+ final ConnectivityManager manager = new ConnectivityManager(mCtx, mService);
+ assertThrows(UnsupportedOperationException.class, () -> manager.tether("iface"));
+ assertThrows(UnsupportedOperationException.class, () -> manager.untether("iface"));
+ }
}