From a6b3b70278fbb9eb76a154d77088e1e51fea1fc6 Mon Sep 17 00:00:00 2001 From: markchien Date: Sun, 19 Apr 2020 22:50:35 +0800 Subject: [PATCH 1/2] Test legacy tether/untether API and onError callback Test legacy tether/untether API behavior. tether() rely on downstream setup ready for tethering. Currently the only user is bluetooth tethering. For other tetherings, there is no guaranteed that calling tether() can always switch given interface to tethered. Tethering may callback with onError depend on the interface status. Caller should use startTethering/stopTethering API instead of these legacy APIs. This change also change the precondition verification of startTethering. If tethering interface got error last time, such interface would not be reported as tetherable. Bug: 150632712 Test: atest CtsTetheringTest Change-Id: Ifb3a0618208ffd0ff224c60f377036bc22ba0565 Merged-In: Ifb3a0618208ffd0ff224c60f377036bc22ba0565 --- .../tethering/cts/TetheringManagerTest.java | 116 ++++++++---------- 1 file changed, 54 insertions(+), 62 deletions(-) diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java index 60f9400363..11ffe2c456 100644 --- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java +++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java @@ -21,6 +21,7 @@ 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,6 +32,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 android.app.UiAutomation; import android.content.BroadcastReceiver; @@ -64,10 +66,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) @@ -140,28 +140,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 +174,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 +261,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 @@ -459,6 +426,21 @@ public class TetheringManagerTest { })); } + public void expectErrorOrTethered(final String iface) { + assertNotNull("No expected callback", mHistory.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 TetheringInterfaceRegexps getTetheringInterfaceRegexps() { return mTetherableRegex; } @@ -497,20 +479,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( @@ -534,9 +509,26 @@ public class TetheringManagerTest { 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 From 668dfa59987ef0a5b17770d77d9569f4d49cfff2 Mon Sep 17 00:00:00 2001 From: markchien Date: Mon, 13 Apr 2020 16:22:12 +0800 Subject: [PATCH 2/2] Add testTetheringUpstream cts test If cellular is the mobile network, tethering should select it as upstream. If DUN is required, tethering should use DUN as its upstream. Bug: 153614366 Bug: 150644305 Test: atest CtsTetheringTest Change-Id: Ief9c745322e8705c0e50185e749a81c5a9d20743 Merged-In: Ief9c745322e8705c0e50185e749a81c5a9d20743 --- tests/cts/tethering/Android.bp | 1 + .../tethering/cts/TetheringManagerTest.java | 142 ++++++++++++++---- 2 files changed, 117 insertions(+), 26 deletions(-) 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 11ffe2c456..bbb9403334 100644 --- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java +++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java @@ -15,6 +15,11 @@ */ 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; @@ -32,6 +37,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.assumeFalse; import static org.junit.Assume.assumeTrue; import android.app.UiAutomation; @@ -39,16 +45,22 @@ 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; @@ -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); @@ -301,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, @@ -324,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; @@ -334,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 @@ -376,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; @@ -385,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; @@ -406,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; @@ -427,7 +449,7 @@ public class TetheringManagerTest { } public void expectErrorOrTethered(final String iface) { - assertNotNull("No expected callback", mHistory.poll(TIMEOUT_MS, (cv) -> { + assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { if (cv.callbackType == CallbackType.ON_ERROR && iface.equals((String) cv.callbackParam)) { return true; @@ -441,6 +463,27 @@ public class TetheringManagerTest { })); } + 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; } @@ -455,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(); @@ -501,11 +545,13 @@ 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); @@ -533,9 +579,8 @@ public class TetheringManagerTest { @Test public void testGetTetherableInterfaceRegexps() { - if (!mTM.isTetheringSupported()) return; - final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback(); + tetherEventCallback.assumeTetheringSupported(); final TetheringInterfaceRegexps tetherableRegexs = tetherEventCallback.getTetheringInterfaceRegexps(); @@ -557,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 @@ -631,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(); + } + } + } }