diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index e81cbf013c..21f36e8531 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.annotation.SystemApi; 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 @@ public class TetheringManager { } } + 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 @@ public class TetheringManager { * 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 @@ public class TetheringManager { @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 @@ public class TetheringManager { /** * 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 4f07f589c4..40b1ec067b 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.WifiP2pGroup; 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 @@ public class Tethering { // 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 @@ public class Tethering { 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 @@ public class Tethering { } 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 9016d132ad..5d99b747b9 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -3065,7 +3065,8 @@ public class ConnectivityManager { *
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.
+ * logic. 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 @@ -3090,7 +3091,8 @@ public class ConnectivityManager { *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.
+ * logic. On SDK versions after {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this will throw + * an UnsupportedOperationException. * * @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 5e94c06acf..2420026942 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.assertFalse; 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 @@ public class TetheringManagerTest { 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 @@ public class TetheringManagerTest { } } } + + @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 9a77c8989d..b41538274d 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.assertNotNull; 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.app.PendingIntent; 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 @@ public class ConnectivityManagerTest { // 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")); + } }