diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp index 9f32403c98..85bb0e03f1 100644 --- a/tests/cts/tethering/Android.bp +++ b/tests/cts/tethering/Android.bp @@ -28,6 +28,7 @@ android_test { "TetheringCommonTests", "TetheringIntegrationTestsLib", "compatibility-device-util-axt", + "cts-net-utils", "net-tests-utils", "ctstestrunner-axt", "junit", diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java index 60f9400363..bbb9403334 100644 --- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java +++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java @@ -15,12 +15,18 @@ */ package android.tethering.test; +import static android.content.pm.PackageManager.FEATURE_TELEPHONY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED; import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED; @@ -31,22 +37,30 @@ 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.assumeFalse; +import static org.junit.Assume.assumeTrue; import android.app.UiAutomation; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.TetheredClient; import android.net.TetheringManager; import android.net.TetheringManager.OnTetheringEntitlementResultListener; import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringInterfaceRegexps; import android.net.TetheringManager.TetheringRequest; +import android.net.cts.util.CtsNetUtils; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.ResultReceiver; +import android.telephony.TelephonyManager; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; @@ -64,10 +78,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @@ -75,11 +87,13 @@ public class TetheringManagerTest { private Context mContext; + private ConnectivityManager mCm; private TetheringManager mTM; + private WifiManager mWm; + private PackageManager mPm; private TetherChangeReceiver mTetherChangeReceiver; - - private String[] mTetheredList; + private CtsNetUtils mCtsNetUtils; private static final int DEFAULT_TIMEOUT_MS = 60_000; @@ -99,7 +113,11 @@ public class TetheringManagerTest { public void setUp() throws Exception { adoptShellPermissionIdentity(); mContext = InstrumentationRegistry.getContext(); + mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); mTM = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE); + mWm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + mPm = mContext.getPackageManager(); + mCtsNetUtils = new CtsNetUtils(mContext); mTetherChangeReceiver = new TetherChangeReceiver(); final IntentFilter filter = new IntentFilter( TetheringManager.ACTION_TETHER_STATE_CHANGED); @@ -140,28 +158,24 @@ public class TetheringManagerTest { public final LinkedBlockingQueue mResult = new LinkedBlockingQueue<>(); - // This method expects either an event where one of the interfaces is active, or events - // where the interfaces are available followed by one event where one of the interfaces is - // active. Here is a typical example for wifi tethering: - // AVAILABLE(wlan0) -> AVAILABLE(wlan1) -> ACTIVATE(wlan1). - public void expectActiveTethering(String[] ifaceRegexs) { - TetherState state = null; + // Expects that tethering reaches the desired state. + // - If active is true, expects that tethering is enabled on at least one interface + // matching ifaceRegexs. + // - If active is false, expects that tethering is disabled on all the interfaces matching + // ifaceRegexs. + // Fails if any interface matching ifaceRegexs becomes errored. + public void expectTethering(final boolean active, final String[] ifaceRegexs) { while (true) { - state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS); - if (state == null) fail("Do not receive active state change broadcast"); + final TetherState state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS, ifaceRegexs); + assertNotNull("Did not receive expected state change, active: " + active, state); - if (isIfaceActive(ifaceRegexs, state)) return; - - if (!isIfaceAvailable(ifaceRegexs, state)) break; + if (isIfaceActive(ifaceRegexs, state) == active) return; } - - fail("Tethering is not actived, available ifaces: " + state.mAvailable.toString() - + ", active ifaces: " + state.mActive.toString()); } - private TetherState pollAndAssertNoError(final int timeout) { + private TetherState pollAndAssertNoError(final int timeout, final String[] ifaceRegexs) { final TetherState state = pollTetherState(timeout); - assertNoErroredIfaces(state); + assertNoErroredIfaces(state, ifaceRegexs); return state; } @@ -178,43 +192,13 @@ public class TetheringManagerTest { return isIfaceMatch(ifaceRegexs, state.mActive); } - private boolean isIfaceAvailable(final String[] ifaceRegexs, final TetherState state) { - return isIfaceMatch(ifaceRegexs, state.mAvailable); - } - - // This method requires a broadcast to have been recorded iff the timeout is non-zero. - public void expectNoActiveTethering(final int timeout) { - final TetherState state = pollAndAssertNoError(timeout); - - if (state == null) { - if (timeout != 0) { - fail("Do not receive tethering state change broadcast"); - } - return; - } - - assertNoActiveIfaces(state); - - for (final TetherState ts : mResult) { - assertNoErroredIfaces(ts); - - assertNoActiveIfaces(ts); - } - } - - private void assertNoErroredIfaces(final TetherState state) { + private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) { if (state == null || state.mErrored == null) return; - if (state.mErrored.size() > 0) { + if (isIfaceMatch(ifaceRegexs, state.mErrored)) { fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray())); } } - - private void assertNoActiveIfaces(final TetherState state) { - if (state.mActive != null && state.mActive.size() > 0) { - fail("Found active tethering interface: " + Arrays.toString(state.mActive.toArray())); - } - } } private static class StartTetheringCallback implements TetheringManager.StartTetheringCallback { @@ -295,17 +279,18 @@ public class TetheringManagerTest { final String[] wifiRegexs = mTM.getTetherableWifiRegexs(); if (wifiRegexs.length == 0) return; - mTetherChangeReceiver.expectNoActiveTethering(0 /** timeout */); + final String[] tetheredIfaces = mTM.getTetheredIfaces(); + assertTrue(tetheredIfaces.length == 0); final StartTetheringCallback startTetheringCallback = new StartTetheringCallback(); mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run() /* executor */, startTetheringCallback); startTetheringCallback.verifyTetheringStarted(); - mTetherChangeReceiver.expectActiveTethering(wifiRegexs); + mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs); mTM.stopTethering(TETHERING_WIFI); - mTetherChangeReceiver.expectNoActiveTethering(DEFAULT_TIMEOUT_MS); + mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs); } @Test @@ -334,6 +319,7 @@ public class TetheringManagerTest { // Must poll the callback before looking at the member. private static class TestTetheringEventCallback implements TetheringEventCallback { private static final int TIMEOUT_MS = 30_000; + public enum CallbackType { ON_SUPPORTED, ON_UPSTREAM, @@ -357,9 +343,11 @@ public class TetheringManagerTest { } } - private final ArrayTrackRecord.ReadHead mHistory = - new ArrayTrackRecord().newReadHead(); + private final ArrayTrackRecord mHistory = + new ArrayTrackRecord(); + private final ArrayTrackRecord.ReadHead mCurrent = + mHistory.newReadHead(); private TetheringInterfaceRegexps mTetherableRegex; private List mTetherableIfaces; @@ -367,7 +355,7 @@ public class TetheringManagerTest { @Override public void onTetheringSupported(boolean supported) { - mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, 0)); + mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0))); } @Override @@ -409,7 +397,7 @@ public class TetheringManagerTest { } public void expectTetherableInterfacesChanged(@NonNull List regexs) { - assertNotNull("No expected tetherable ifaces callback", mHistory.poll(TIMEOUT_MS, + assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false; final List interfaces = (List) cv.callbackParam; @@ -418,7 +406,7 @@ public class TetheringManagerTest { } public void expectTetheredInterfacesChanged(@NonNull List regexs) { - assertNotNull("No expected tethered ifaces callback", mHistory.poll(TIMEOUT_MS, + assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false; @@ -439,17 +427,18 @@ public class TetheringManagerTest { // Receive ON_ERROR on started callback is not matter. It just means tethering is // failed last time, should able to continue the test this time. while ((receivedBitMap & expectedBitMap) != expectedBitMap) { - final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true); + final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true); if (cv == null) { fail("No expected callbacks, " + "expected bitmap: " + expectedBitMap + ", actual: " + receivedBitMap); } + receivedBitMap |= (1 << cv.callbackType.ordinal()); } } public void expectOneOfOffloadStatusChanged(int... offloadStatuses) { - assertNotNull("No offload status changed", mHistory.poll(TIMEOUT_MS, (cv) -> { + assertNotNull("No offload status changed", mCurrent.poll(TIMEOUT_MS, (cv) -> { if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false; final int status = (int) cv.callbackParam; @@ -459,6 +448,42 @@ public class TetheringManagerTest { })); } + public void expectErrorOrTethered(final String iface) { + assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { + if (cv.callbackType == CallbackType.ON_ERROR + && iface.equals((String) cv.callbackParam)) { + return true; + } + if (cv.callbackType == CallbackType.ON_TETHERED_IFACES + && ((List) cv.callbackParam).contains(iface)) { + return true; + } + + return false; + })); + } + + public Network getCurrentValidUpstream() { + final CallbackValue result = mCurrent.poll(TIMEOUT_MS, (cv) -> { + return (cv.callbackType == CallbackType.ON_UPSTREAM) + && cv.callbackParam != null; + }); + + assertNotNull("No valid upstream", result); + return (Network) result.callbackParam; + } + + public void assumeTetheringSupported() { + final ArrayTrackRecord.ReadHead history = + mHistory.newReadHead(); + assertNotNull("No onSupported callback", history.poll(TIMEOUT_MS, (cv) -> { + if (cv.callbackType != CallbackType.ON_SUPPORTED) return false; + + assumeTrue(cv.callbackParam2 == 1 /* supported */); + return true; + })); + } + public TetheringInterfaceRegexps getTetheringInterfaceRegexps() { return mTetherableRegex; } @@ -473,7 +498,8 @@ public class TetheringManagerTest { } private TestTetheringEventCallback registerTetheringEventCallback() { - final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback(); + final TestTetheringEventCallback tetherEventCallback = + new TestTetheringEventCallback(); mTM.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback); tetherEventCallback.expectCallbackStarted(); @@ -497,20 +523,13 @@ public class TetheringManagerTest { private void startWifiTethering(final TestTetheringEventCallback callback) throws InterruptedException { final List wifiRegexs = getWifiTetherableInterfaceRegexps(callback); - final boolean isIfaceAvailWhenNoTethering = - isIfaceMatch(wifiRegexs, callback.getTetherableInterfaces()); + assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces())); final StartTetheringCallback startTetheringCallback = new StartTetheringCallback(); mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run() /* executor */, startTetheringCallback); startTetheringCallback.verifyTetheringStarted(); - // If interface is already available before starting tethering, the available callback may - // not be sent after tethering enabled. - if (!isIfaceAvailWhenNoTethering) { - callback.expectTetherableInterfacesChanged(wifiRegexs); - } - callback.expectTetheredInterfacesChanged(wifiRegexs); callback.expectOneOfOffloadStatusChanged( @@ -526,24 +545,42 @@ public class TetheringManagerTest { @Test public void testRegisterTetheringEventCallback() throws Exception { - if (!mTM.isTetheringSupported()) return; - final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback(); + tetherEventCallback.assumeTetheringSupported(); - if (!isWifiTetheringSupported(tetherEventCallback)) return; + if (!isWifiTetheringSupported(tetherEventCallback)) { + unregisterTetheringEventCallback(tetherEventCallback); + return; + } startWifiTethering(tetherEventCallback); + final List tetheredIfaces = tetherEventCallback.getTetheredInterfaces(); + assertEquals(1, tetheredIfaces.size()); + final String wifiTetheringIface = tetheredIfaces.get(0); + stopWifiTethering(tetherEventCallback); - unregisterTetheringEventCallback(tetherEventCallback); + try { + final int ret = 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. + assumeTrue(ret == TETHER_ERROR_NO_ERROR); + + // If calling #tether successful, there is a callback to tell the result of tethering + // setup. + tetherEventCallback.expectErrorOrTethered(wifiTetheringIface); + } finally { + mTM.untether(wifiTetheringIface); + unregisterTetheringEventCallback(tetherEventCallback); + } } @Test public void testGetTetherableInterfaceRegexps() { - if (!mTM.isTetheringSupported()) return; - final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback(); + tetherEventCallback.assumeTetheringSupported(); final TetheringInterfaceRegexps tetherableRegexs = tetherEventCallback.getTetheringInterfaceRegexps(); @@ -565,21 +602,22 @@ public class TetheringManagerTest { @Test public void testStopAllTethering() throws Exception { - if (!mTM.isTetheringSupported()) return; - final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback(); + tetherEventCallback.assumeTetheringSupported(); - if (!isWifiTetheringSupported(tetherEventCallback)) return; + try { + if (!isWifiTetheringSupported(tetherEventCallback)) return; - // TODO: start ethernet tethering here when TetheringManagerTest is moved to - // TetheringIntegrationTest. + // TODO: start ethernet tethering here when TetheringManagerTest is moved to + // TetheringIntegrationTest. - startWifiTethering(tetherEventCallback); + startWifiTethering(tetherEventCallback); - mTM.stopAllTethering(); - tetherEventCallback.expectTetheredInterfacesChanged(null); - - unregisterTetheringEventCallback(tetherEventCallback); + mTM.stopAllTethering(); + tetherEventCallback.expectTetheredInterfacesChanged(null); + } finally { + unregisterTetheringEventCallback(tetherEventCallback); + } } @Test @@ -639,4 +677,48 @@ public class TetheringManagerTest { TETHERING_WIFI, false, c -> c.run(), null); } catch (IllegalArgumentException expect) { } } + + @Test + public void testTetheringUpstream() throws Exception { + assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY)); + final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback(); + tetherEventCallback.assumeTetheringSupported(); + final boolean previousWifiEnabledState = mWm.isWifiEnabled(); + + try { + if (!isWifiTetheringSupported(tetherEventCallback)) return; + + if (previousWifiEnabledState) { + mCtsNetUtils.disconnectFromWifi(null); + } + + final Network activeNetwork = mCm.getActiveNetwork(); + assertNotNull("No active network. Please ensure the device has working mobile data.", + activeNetwork); + final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork); + + // If active nework is ETHERNET, tethering may not use cell network as upstream. + assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET)); + + assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR)); + + startWifiTethering(tetherEventCallback); + + final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + final boolean dunRequired = telephonyManager.isTetheringApnRequired(); + final int expectedCap = dunRequired ? NET_CAPABILITY_DUN : NET_CAPABILITY_INTERNET; + final Network network = tetherEventCallback.getCurrentValidUpstream(); + final NetworkCapabilities netCap = mCm.getNetworkCapabilities(network); + assertTrue(netCap.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(netCap.hasCapability(expectedCap)); + + stopWifiTethering(tetherEventCallback); + } finally { + unregisterTetheringEventCallback(tetherEventCallback); + if (previousWifiEnabledState) { + mCtsNetUtils.connectToWifi(); + } + } + } }