diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index de94cba5f5..f1ddc6dffa 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -80,6 +80,7 @@ import java.nio.ByteBuffer; import java.util.Collection; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -349,7 +350,7 @@ public class EthernetTetheringTest { private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1); private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1); private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1); - private final String mIface; + private final TetheringInterface mIface; private volatile boolean mInterfaceWasTethered = false; private volatile boolean mInterfaceWasLocalOnly = false; @@ -358,20 +359,24 @@ public class EthernetTetheringTest { MyTetheringEventCallback(TetheringManager tm, String iface) { mTm = tm; - mIface = iface; + mIface = new TetheringInterface(TETHERING_ETHERNET, iface); } public void unregister() { mTm.unregisterTetheringEventCallback(this); mUnregistered = true; } - @Override public void onTetheredInterfacesChanged(List interfaces) { + fail("Should only call callback that takes a Set"); + } + + @Override + public void onTetheredInterfacesChanged(Set interfaces) { // Ignore stale callbacks registered by previous test cases. if (mUnregistered) return; - if (!mInterfaceWasTethered && (mIface == null || interfaces.contains(mIface))) { + if (!mInterfaceWasTethered && interfaces.contains(mIface)) { // This interface is being tethered for the first time. Log.d(TAG, "Tethering started: " + interfaces); mInterfaceWasTethered = true; @@ -384,10 +389,15 @@ public class EthernetTetheringTest { @Override public void onLocalOnlyInterfacesChanged(List interfaces) { + fail("Should only call callback that takes a Set"); + } + + @Override + public void onLocalOnlyInterfacesChanged(Set interfaces) { // Ignore stale callbacks registered by previous test cases. if (mUnregistered) return; - if (!mInterfaceWasLocalOnly && (mIface == null || interfaces.contains(mIface))) { + if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) { // This interface is being put into local-only mode for the first time. Log.d(TAG, "Local-only started: " + interfaces); mInterfaceWasLocalOnly = true; diff --git a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java index 7ffe37ad64..e0fcbfad28 100644 --- a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java +++ b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java @@ -20,12 +20,12 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.READ_DEVICE_CONFIG; import static android.Manifest.permission.TETHER_PRIVILEGED; import static android.Manifest.permission.WRITE_SETTINGS; +import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -35,6 +35,7 @@ import android.app.UiAutomation; import android.content.Context; import android.net.IpPrefix; import android.net.LinkAddress; +import android.net.TetheringInterface; import android.net.TetheringManager; import android.net.cts.util.CtsTetheringUtils; import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback; @@ -102,12 +103,13 @@ public class TetheringModuleTest { try { tetherEventCallback.assumeTetheringSupported(); assumeTrue(isWifiTetheringSupported(tetherEventCallback)); + tetherEventCallback.expectNoTetheringActive(); - mCtsTetheringUtils.startWifiTethering(tetherEventCallback); + final TetheringInterface tetheredIface = + mCtsTetheringUtils.startWifiTethering(tetherEventCallback); - final List tetheredIfaces = tetherEventCallback.getTetheredInterfaces(); - assertEquals(1, tetheredIfaces.size()); - final String wifiTetheringIface = tetheredIfaces.get(0); + assertNotNull(tetheredIface); + final String wifiTetheringIface = tetheredIface.getInterface(); NetworkInterface nif = NetworkInterface.getByName(wifiTetheringIface); // Tethering downstream only have one ipv4 address. @@ -120,11 +122,11 @@ public class TetheringModuleTest { tnt = setUpTestNetwork( new LinkAddress(testPrefix.getAddress(), testPrefix.getPrefixLength())); - tetherEventCallback.expectTetheredInterfacesChanged(null); + tetherEventCallback.expectNoTetheringActive(); final List wifiRegexs = tetherEventCallback.getTetheringInterfaceRegexps().getTetherableWifiRegexs(); - tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs); + tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI); nif = NetworkInterface.getByName(wifiTetheringIface); final LinkAddress newHotspotAddr = getFirstIpv4Address(nif); assertNotNull(newHotspotAddr); diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java index c95dc28dd1..1bdd5337b3 100644 --- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java +++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java @@ -22,7 +22,7 @@ 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; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -35,6 +35,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.Network; import android.net.TetheredClient; +import android.net.TetheringInterface; import android.net.TetheringManager; import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringInterfaceRegexps; @@ -51,6 +52,7 @@ import com.android.net.module.util.ArrayTrackRecord; import java.util.Collection; import java.util.List; +import java.util.Set; public final class CtsTetheringUtils { private TetheringManager mTm; @@ -116,23 +118,38 @@ public final class CtsTetheringUtils { } } - public static boolean isIfaceMatch(final List ifaceRegexs, final List ifaces) { - return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces); - } - - public static boolean isIfaceMatch(final String[] ifaceRegexs, final List ifaces) { + private static boolean isRegexMatch(final String[] ifaceRegexs, String iface) { if (ifaceRegexs == null) fail("ifaceRegexs should not be null"); + for (String regex : ifaceRegexs) { + if (iface.matches(regex)) return true; + } + + return false; + } + + public static boolean isAnyIfaceMatch(final String[] ifaceRegexs, final List ifaces) { if (ifaces == null) return false; for (String s : ifaces) { - for (String regex : ifaceRegexs) { - if (s.matches(regex)) { - return true; - } + if (isRegexMatch(ifaceRegexs, s)) return true; + } + + return false; + } + + private static TetheringInterface getFirstMatchingTetheringInterface(final List regexs, + final int type, final Set ifaces) { + if (ifaces == null || regexs == null) return null; + + final String[] regexArray = regexs.toArray(new String[0]); + for (TetheringInterface iface : ifaces) { + if (isRegexMatch(regexArray, iface.getInterface()) && type == iface.getType()) { + return iface; } } - return false; + + return null; } // Must poll the callback before looking at the member. @@ -171,6 +188,8 @@ public final class CtsTetheringUtils { private TetheringInterfaceRegexps mTetherableRegex; private List mTetherableIfaces; private List mTetheredIfaces; + private String mErrorIface; + private int mErrorCode; @Override public void onTetheringSupported(boolean supported) { @@ -191,17 +210,41 @@ public final class CtsTetheringUtils { @Override public void onTetherableInterfacesChanged(List interfaces) { mTetherableIfaces = interfaces; + } + // Call the interface default implementation, which will call + // onTetherableInterfacesChanged(List). This ensures that the default implementation + // of the new callback method calls the old callback method and avoids the need to convert + // Set to List in this code. + @Override + public void onTetherableInterfacesChanged(Set interfaces) { + TetheringEventCallback.super.onTetherableInterfacesChanged(interfaces); + assertHasAllTetheringInterfaces(interfaces, mTetherableIfaces); mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0)); } @Override public void onTetheredInterfacesChanged(List interfaces) { mTetheredIfaces = interfaces; + } + + @Override + public void onTetheredInterfacesChanged(Set interfaces) { + TetheringEventCallback.super.onTetheredInterfacesChanged(interfaces); + assertHasAllTetheringInterfaces(interfaces, mTetheredIfaces); mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0)); } @Override public void onError(String ifName, int error) { + mErrorIface = ifName; + mErrorCode = error; + } + + @Override + public void onError(TetheringInterface ifName, int error) { + TetheringEventCallback.super.onError(ifName, error); + assertEquals(ifName.getInterface(), mErrorIface); + assertEquals(error, mErrorCode); mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error)); } @@ -215,30 +258,66 @@ public final class CtsTetheringUtils { mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0)); } - public void expectTetherableInterfacesChanged(@NonNull List regexs) { + private void assertHasAllTetheringInterfaces(Set tetheringIfaces, + List ifaces) { + // This does not check that the interfaces are the same. This checks that the + // List has all the interface names contained by the Set. + assertEquals(tetheringIfaces.size(), ifaces.size()); + for (TetheringInterface tether : tetheringIfaces) { + assertTrue("iface " + tether.getInterface() + + " seen by new callback but not old callback", + ifaces.contains(tether.getInterface())); + } + } + + public void expectTetherableInterfacesChanged(@NonNull final List regexs, + final int type) { 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; - return isIfaceMatch(regexs, interfaces); + final Set interfaces = + (Set) cv.callbackParam; + return getFirstMatchingTetheringInterface(regexs, type, interfaces) != null; })); } - public void expectTetheredInterfacesChanged(@NonNull List regexs) { - assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS, - (cv) -> { - if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false; + public void expectNoTetheringActive() { + assertNotNull("At least one tethering type unexpectedly active", + mCurrent.poll(TIMEOUT_MS, (cv) -> { + if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false; - final List interfaces = (List) cv.callbackParam; + return ((Set) cv.callbackParam).isEmpty(); + })); + } - // Null regexs means no active tethering. - if (regexs == null) return interfaces.isEmpty(); + public TetheringInterface expectTetheredInterfacesChanged( + @NonNull final List regexs, final int type) { + while (true) { + final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true); + if (cv == null) { + fail("No expected tethered ifaces callback, expected type: " + type); + } - return isIfaceMatch(regexs, interfaces); - })); + if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue; + + final Set interfaces = + (Set) cv.callbackParam; + + final TetheringInterface iface = + getFirstMatchingTetheringInterface(regexs, type, interfaces); + + if (iface != null) return iface; + } } public void expectCallbackStarted() { + // This method uses its own readhead because it just check whether last tethering status + // is updated after TetheringEventCallback get registered but do not check content + // of received callbacks. Using shared readhead (mCurrent) only when the callbacks the + // method polled is also not necessary for other methods which using shared readhead. + // All of methods using mCurrent is order mattered. + final ArrayTrackRecord.ReadHead history = + mHistory.newReadHead(); int receivedBitMap = 0; // The each bit represent a type from CallbackType.ON_*. // Expect all of callbacks except for ON_ERROR. @@ -246,7 +325,7 @@ public final class CtsTetheringUtils { // 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 = mCurrent.poll(TIMEOUT_MS, c -> true); + final CallbackValue cv = history.poll(TIMEOUT_MS, c -> true); if (cv == null) { fail("No expected callbacks, " + "expected bitmap: " + expectedBitMap + ", actual: " + receivedBitMap); @@ -269,14 +348,14 @@ public final class CtsTetheringUtils { })); } - public void expectErrorOrTethered(final String iface) { + public void expectErrorOrTethered(final TetheringInterface iface) { assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> { if (cv.callbackType == CallbackType.ON_ERROR - && iface.equals((String) cv.callbackParam)) { + && iface.equals((TetheringInterface) cv.callbackParam)) { return true; } if (cv.callbackType == CallbackType.ON_TETHERED_IFACES - && ((List) cv.callbackParam).contains(iface)) { + && ((Set) cv.callbackParam).contains(iface)) { return true; } @@ -328,14 +407,6 @@ public final class CtsTetheringUtils { public TetheringInterfaceRegexps getTetheringInterfaceRegexps() { return mTetherableRegex; } - - public List getTetherableInterfaces() { - return mTetherableIfaces; - } - - public List getTetheredInterfaces() { - return mTetheredIfaces; - } } private static void waitForWifiEnabled(final Context ctx) throws Exception { @@ -386,10 +457,9 @@ public final class CtsTetheringUtils { return !getWifiTetherableInterfaceRegexps(callback).isEmpty(); } - public void startWifiTethering(final TestTetheringEventCallback callback) + public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback) throws InterruptedException { final List wifiRegexs = getWifiTetherableInterfaceRegexps(callback); - assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces())); final StartTetheringCallback startTetheringCallback = new StartTetheringCallback(); final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI) @@ -397,11 +467,14 @@ public final class CtsTetheringUtils { mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback); startTetheringCallback.verifyTetheringStarted(); - callback.expectTetheredInterfacesChanged(wifiRegexs); + final TetheringInterface iface = + callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI); callback.expectOneOfOffloadStatusChanged( TETHER_HARDWARE_OFFLOAD_STARTED, TETHER_HARDWARE_OFFLOAD_FAILED); + + return iface; } private static class StopSoftApCallback implements SoftApCallback { @@ -441,7 +514,7 @@ public final class CtsTetheringUtils { public void stopWifiTethering(final TestTetheringEventCallback callback) { mTm.stopTethering(TETHERING_WIFI); expectSoftApDisabled(); - callback.expectTetheredInterfacesChanged(null); + callback.expectNoTetheringActive(); callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED); } } diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java index 71a81ff621..0a5e506c07 100644 --- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java +++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java @@ -26,8 +26,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.cts.util.CtsTetheringUtils.isIfaceMatch; -import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported; +import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -48,6 +47,7 @@ import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.TetheringInterface; import android.net.TetheringManager; import android.net.TetheringManager.OnTetheringEntitlementResultListener; import android.net.TetheringManager.TetheringInterfaceRegexps; @@ -190,13 +190,13 @@ public class TetheringManagerTest { } private boolean isIfaceActive(final String[] ifaceRegexs, final TetherState state) { - return isIfaceMatch(ifaceRegexs, state.mActive); + return isAnyIfaceMatch(ifaceRegexs, state.mActive); } private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) { if (state == null || state.mErrored == null) return; - if (isIfaceMatch(ifaceRegexs, state.mErrored)) { + if (isAnyIfaceMatch(ifaceRegexs, state.mErrored)) { fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray())); } } @@ -256,12 +256,13 @@ public class TetheringManagerTest { try { tetherEventCallback.assumeWifiTetheringSupported(mContext); + tetherEventCallback.expectNoTetheringActive(); - mCtsTetheringUtils.startWifiTethering(tetherEventCallback); + final TetheringInterface tetheredIface = + mCtsTetheringUtils.startWifiTethering(tetherEventCallback); - final List tetheredIfaces = tetherEventCallback.getTetheredInterfaces(); - assertEquals(1, tetheredIfaces.size()); - final String wifiTetheringIface = tetheredIfaces.get(0); + assertNotNull(tetheredIface); + final String wifiTetheringIface = tetheredIface.getInterface(); mCtsTetheringUtils.stopWifiTethering(tetherEventCallback); @@ -272,7 +273,8 @@ public class TetheringManagerTest { if (ret == TETHER_ERROR_NO_ERROR) { // If calling #tether successful, there is a callback to tell the result of // tethering setup. - tetherEventCallback.expectErrorOrTethered(wifiTetheringIface); + tetherEventCallback.expectErrorOrTethered( + new TetheringInterface(TETHERING_WIFI, wifiTetheringIface)); } } finally { mTM.untether(wifiTetheringIface); @@ -319,7 +321,7 @@ public class TetheringManagerTest { mCtsTetheringUtils.startWifiTethering(tetherEventCallback); mTM.stopAllTethering(); - tetherEventCallback.expectTetheredInterfacesChanged(null); + tetherEventCallback.expectNoTetheringActive(); } finally { mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback); } @@ -417,6 +419,7 @@ public class TetheringManagerTest { try { tetherEventCallback.assumeWifiTetheringSupported(mContext); + tetherEventCallback.expectNoTetheringActive(); previousWifiEnabledState = mWm.isWifiEnabled(); if (previousWifiEnabledState) {