diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java index 86fe54ce54..b132982e1a 100644 --- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java +++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java @@ -29,9 +29,14 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.LinkAddress; +import android.net.Network; +import android.net.TetheredClient; import android.net.TetheringManager; +import android.net.TetheringManager.TetheringEventCallback; +import android.net.TetheringManager.TetheringInterfaceRegexps; import android.net.TetheringManager.TetheringRequest; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -42,6 +47,8 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -195,8 +202,12 @@ public class TetheringManagerTest { } } - private static boolean isIfaceMatch(final String[] ifaceRegexs, - final ArrayList ifaces) { + private static boolean isIfaceMatch(final List ifaceRegexs, + final List ifaces) { + return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces); + } + + private static boolean isIfaceMatch(final String[] ifaceRegexs, final List ifaces) { if (ifaceRegexs == null) fail("ifaceRegexs should not be null"); if (ifaces == null) return false; @@ -251,4 +262,170 @@ public class TetheringManagerTest { assertTrue(tr2.isExemptFromEntitlementCheck()); assertFalse(tr2.getShouldShowEntitlementUi()); } + + // Must poll the callback before looking at the member. + private static class TestTetheringEventCallback implements TetheringEventCallback { + public enum CallbackType { + ON_SUPPORTED, + ON_UPSTREAM, + ON_TETHERABLE_REGEX, + ON_TETHERABLE_IFACES, + ON_TETHERED_IFACES, + ON_ERROR, + ON_CLIENTS, + }; + + public static class CallbackValue { + public final CallbackType callbackType; + public final Object callbackParam; + public final int callbackParam2; + + private CallbackValue(final CallbackType type, final Object param, final int param2) { + this.callbackType = type; + this.callbackParam = param; + this.callbackParam2 = param2; + } + } + private final LinkedBlockingQueue mCallbacks = new LinkedBlockingQueue<>(); + + private TetheringInterfaceRegexps mTetherableRegex; + private List mTetherableIfaces; + private List mTetheredIfaces; + + @Override + public void onTetheringSupported(boolean supported) { + mCallbacks.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, 0)); + } + + @Override + public void onUpstreamChanged(Network network) { + mCallbacks.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0)); + } + + @Override + public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) { + mTetherableRegex = reg; + mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0)); + } + + @Override + public void onTetherableInterfacesChanged(List interfaces) { + mTetherableIfaces = interfaces; + mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0)); + } + + @Override + public void onTetheredInterfacesChanged(List interfaces) { + mTetheredIfaces = interfaces; + mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0)); + } + + @Override + public void onError(String ifName, int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error)); + } + + @Override + public void onClientsChanged(Collection clients) { + mCallbacks.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0)); + } + + public CallbackValue pollCallback() { + try { + return mCallbacks.poll(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail("Callback not seen"); + } + return null; + } + + public void expectTetherableInterfacesChanged(@NonNull List regexs) { + while (true) { + final CallbackValue cv = pollCallback(); + if (cv == null) fail("No expected tetherable ifaces callback"); + if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) continue; + + final List interfaces = (List) cv.callbackParam; + if (isIfaceMatch(regexs, interfaces)) break; + } + } + + public void expectTetheredInterfacesChanged(@NonNull List regexs) { + while (true) { + final CallbackValue cv = pollCallback(); + if (cv == null) fail("No expected tethered ifaces callback"); + if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue; + + final List interfaces = (List) cv.callbackParam; + + // Null regexs means no active tethering. + if (regexs == null) { + if (interfaces.size() == 0) break; + } else if (isIfaceMatch(regexs, interfaces)) { + break; + } + } + } + + public void expectCallbackStarted() { + // The each bit represent a type from CallbackType.ON_*. + // Expect all of callbacks except for ON_ERROR. + final int expectedBitMap = 0x7f ^ (1 << CallbackType.ON_ERROR.ordinal()); + int receivedBitMap = 0; + while (receivedBitMap != expectedBitMap) { + final CallbackValue cv = pollCallback(); + if (cv == null) { + fail("No expected callbacks, " + "expected bitmap: " + + expectedBitMap + ", actual: " + receivedBitMap); + } + receivedBitMap = receivedBitMap | (1 << cv.callbackType.ordinal()); + } + } + + public TetheringInterfaceRegexps getTetheringInterfaceRegexps() { + return mTetherableRegex; + } + + public List getTetherableInterfaces() { + return mTetherableIfaces; + } + + public List getTetheredInterfaces() { + return mTetheredIfaces; + } + } + + @Test + public void testRegisterTetheringEventCallback() throws Exception { + if (!mTM.isTetheringSupported()) return; + + final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback(); + + mTM.registerTetheringEventCallback(c -> c.run(), tetherEventCallback); + tetherEventCallback.expectCallbackStarted(); + + final TetheringInterfaceRegexps tetherableRegexs = + tetherEventCallback.getTetheringInterfaceRegexps(); + final List wifiRegexs = tetherableRegexs.getTetherableWifiRegexs(); + if (wifiRegexs.size() == 0) return; + + final boolean isIfaceAvailWhenNoTethering = + isIfaceMatch(wifiRegexs, tetherEventCallback.getTetherableInterfaces()); + + mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(), + new StartTetheringCallback()); + + // If interface is already available before starting tethering, the available callback may + // not be sent after tethering enabled. + if (!isIfaceAvailWhenNoTethering) { + tetherEventCallback.expectTetherableInterfacesChanged(wifiRegexs); + } + + tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs); + + mTM.stopTethering(TETHERING_WIFI); + + tetherEventCallback.expectTetheredInterfacesChanged(null); + mTM.unregisterTetheringEventCallback(tetherEventCallback); + } }