Migrate Tethering util functions to CtsTetheringUtils

Bug: 166057846
Bug: 170265597
Test: atest MtsTetheringTest
      atest CtsTetheringTest
Change-Id: I59d529cb50b4b6cdafc6be78ad61a55ee1be0404
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index 1f94613..c36d976 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -21,5 +21,6 @@
     static_libs: [
         "compatibility-device-util-axt",
         "junit",
+        "net-tests-utils",
     ],
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..b18c1e7
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.util;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_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;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+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 android.net.wifi.WifiClient;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.SoftApCallback;
+import android.os.ConditionVariable;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.ArrayTrackRecord;
+
+import java.util.Collection;
+import java.util.List;
+
+public final class CtsTetheringUtils {
+    private TetheringManager mTm;
+    private WifiManager mWm;
+    private Context mContext;
+
+    private static final int DEFAULT_TIMEOUT_MS = 60_000;
+
+    public CtsTetheringUtils(Context ctx) {
+        mContext = ctx;
+        mTm = mContext.getSystemService(TetheringManager.class);
+        mWm = mContext.getSystemService(WifiManager.class);
+    }
+
+    public static class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
+        private static int TIMEOUT_MS = 30_000;
+        public static class CallbackValue {
+            public final int error;
+
+            private CallbackValue(final int e) {
+                error = e;
+            }
+
+            public static class OnTetheringStarted extends CallbackValue {
+                OnTetheringStarted() { super(TETHER_ERROR_NO_ERROR); }
+            }
+
+            public static class OnTetheringFailed extends CallbackValue {
+                OnTetheringFailed(final int error) { super(error); }
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%d)", getClass().getSimpleName(), error);
+            }
+        }
+
+        private final ArrayTrackRecord<CallbackValue>.ReadHead mHistory =
+                new ArrayTrackRecord<CallbackValue>().newReadHead();
+
+        @Override
+        public void onTetheringStarted() {
+            mHistory.add(new CallbackValue.OnTetheringStarted());
+        }
+
+        @Override
+        public void onTetheringFailed(final int error) {
+            mHistory.add(new CallbackValue.OnTetheringFailed(error));
+        }
+
+        public void verifyTetheringStarted() {
+            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+            assertNotNull("No onTetheringStarted after " + TIMEOUT_MS + " ms", cv);
+            assertTrue("Fail start tethering:" + cv,
+                    cv instanceof CallbackValue.OnTetheringStarted);
+        }
+
+        public void expectTetheringFailed(final int expected) throws InterruptedException {
+            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
+            assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv);
+            assertTrue("Expect fail with error code " + expected + ", but received: " + cv,
+                    (cv instanceof CallbackValue.OnTetheringFailed) && (cv.error == expected));
+        }
+    }
+
+    public static boolean isIfaceMatch(final List<String> ifaceRegexs, final List<String> ifaces) {
+        return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
+    }
+
+    public static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
+        if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
+
+        if (ifaces == null) return false;
+
+        for (String s : ifaces) {
+            for (String regex : ifaceRegexs) {
+                if (s.matches(regex)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // Must poll the callback before looking at the member.
+    public static class TestTetheringEventCallback implements TetheringEventCallback {
+        private static final int TIMEOUT_MS = 30_000;
+
+        public enum CallbackType {
+            ON_SUPPORTED,
+            ON_UPSTREAM,
+            ON_TETHERABLE_REGEX,
+            ON_TETHERABLE_IFACES,
+            ON_TETHERED_IFACES,
+            ON_ERROR,
+            ON_CLIENTS,
+            ON_OFFLOAD_STATUS,
+        };
+
+        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 ArrayTrackRecord<CallbackValue> mHistory =
+                new ArrayTrackRecord<CallbackValue>();
+
+        private final ArrayTrackRecord<CallbackValue>.ReadHead mCurrent =
+                mHistory.newReadHead();
+
+        private TetheringInterfaceRegexps mTetherableRegex;
+        private List<String> mTetherableIfaces;
+        private List<String> mTetheredIfaces;
+
+        @Override
+        public void onTetheringSupported(boolean supported) {
+            mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0)));
+        }
+
+        @Override
+        public void onUpstreamChanged(Network network) {
+            mHistory.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
+        }
+
+        @Override
+        public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) {
+            mTetherableRegex = reg;
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
+        }
+
+        @Override
+        public void onTetherableInterfacesChanged(List<String> interfaces) {
+            mTetherableIfaces = interfaces;
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
+        }
+
+        @Override
+        public void onTetheredInterfacesChanged(List<String> interfaces) {
+            mTetheredIfaces = interfaces;
+            mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
+        }
+
+        @Override
+        public void onError(String ifName, int error) {
+            mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
+        }
+
+        @Override
+        public void onClientsChanged(Collection<TetheredClient> clients) {
+            mHistory.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
+        }
+
+        @Override
+        public void onOffloadStatusChanged(int status) {
+            mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
+        }
+
+        public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
+            assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
+                (cv) -> {
+                    if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
+                    final List<String> interfaces = (List<String>) cv.callbackParam;
+                    return isIfaceMatch(regexs, interfaces);
+                }));
+        }
+
+        public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
+            assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
+                (cv) -> {
+                    if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
+
+                    final List<String> interfaces = (List<String>) cv.callbackParam;
+
+                    // Null regexs means no active tethering.
+                    if (regexs == null) return interfaces.isEmpty();
+
+                    return isIfaceMatch(regexs, interfaces);
+                }));
+        }
+
+        public void expectCallbackStarted() {
+            int receivedBitMap = 0;
+            // The each bit represent a type from CallbackType.ON_*.
+            // Expect all of callbacks except for ON_ERROR.
+            final int expectedBitMap = 0xff ^ (1 << CallbackType.ON_ERROR.ordinal());
+            // 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);
+                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", mCurrent.poll(TIMEOUT_MS, (cv) -> {
+                if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false;
+
+                final int status = (int) cv.callbackParam;
+                for (int offloadStatus : offloadStatuses) {
+                    if (offloadStatus == status) return true;
+                }
+
+                return false;
+            }));
+        }
+
+        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<String>) 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<CallbackValue>.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;
+        }
+
+        public List<String> getTetherableInterfaces() {
+            return mTetherableIfaces;
+        }
+
+        public List<String> getTetheredInterfaces() {
+            return mTetheredIfaces;
+        }
+    }
+
+    public TestTetheringEventCallback registerTetheringEventCallback() {
+        final TestTetheringEventCallback tetherEventCallback =
+                new TestTetheringEventCallback();
+
+        mTm.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
+        tetherEventCallback.expectCallbackStarted();
+
+        return tetherEventCallback;
+    }
+
+    public void unregisterTetheringEventCallback(final TestTetheringEventCallback callback) {
+        mTm.unregisterTetheringEventCallback(callback);
+    }
+
+    private static List<String> getWifiTetherableInterfaceRegexps(
+            final TestTetheringEventCallback callback) {
+        return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
+    }
+
+    public static boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
+        return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
+    }
+
+    public void startWifiTethering(final TestTetheringEventCallback callback)
+            throws InterruptedException {
+        final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
+        assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
+
+        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+                .setShouldShowEntitlementUi(false).build();
+        mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
+        startTetheringCallback.verifyTetheringStarted();
+
+        callback.expectTetheredInterfacesChanged(wifiRegexs);
+
+        callback.expectOneOfOffloadStatusChanged(
+                TETHER_HARDWARE_OFFLOAD_STARTED,
+                TETHER_HARDWARE_OFFLOAD_FAILED);
+    }
+
+    private static class StopSoftApCallback implements SoftApCallback {
+        private final ConditionVariable mWaiting = new ConditionVariable();
+        @Override
+        public void onStateChanged(int state, int failureReason) {
+            if (state == WifiManager.WIFI_AP_STATE_DISABLED) mWaiting.open();
+        }
+
+        @Override
+        public void onConnectedClientsChanged(List<WifiClient> clients) { }
+
+        public void waitForSoftApStopped() {
+            if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+                fail("stopSoftAp Timeout");
+            }
+        }
+    }
+
+    // Wait for softAp to be disabled. This is necessary on devices where stopping softAp
+    // deletes the interface. On these devices, tethering immediately stops when the softAp
+    // interface is removed, but softAp is not yet fully disabled. Wait for softAp to be
+    // fully disabled, because otherwise the next test might fail because it attempts to
+    // start softAp before it's fully stopped.
+    public void expectSoftApDisabled() {
+        final StopSoftApCallback callback = new StopSoftApCallback();
+        try {
+            mWm.registerSoftApCallback(c -> c.run(), callback);
+            // registerSoftApCallback will immediately call the callback with the current state, so
+            // this callback will fire even if softAp is already disabled.
+            callback.waitForSoftApStopped();
+        } finally {
+            mWm.unregisterSoftApCallback(callback);
+        }
+    }
+
+    public void stopWifiTethering(final TestTetheringEventCallback callback) {
+        mTm.stopTethering(TETHERING_WIFI);
+        expectSoftApDisabled();
+        callback.expectTetheredInterfacesChanged(null);
+        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 92dca39..397b4b8 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,9 +26,8 @@
 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_HARDWARE_OFFLOAD_FAILED;
-import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
-import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
+import static android.net.cts.util.CtsTetheringUtils.isIfaceMatch;
+import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,31 +48,26 @@
 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.cts.util.CtsNetUtils.TestNetworkCallback;
-import android.net.wifi.WifiClient;
+import android.net.cts.util.CtsTetheringUtils;
+import android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
+import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.SoftApCallback;
 import android.os.Bundle;
-import android.os.ConditionVariable;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
-import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.ArrayTrackRecord;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -81,7 +75,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -100,6 +93,7 @@
 
     private TetherChangeReceiver mTetherChangeReceiver;
     private CtsNetUtils mCtsNetUtils;
+    private CtsTetheringUtils mCtsTetheringUtils;
 
     private static final int DEFAULT_TIMEOUT_MS = 60_000;
 
@@ -124,6 +118,7 @@
         mWm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mPm = mContext.getPackageManager();
         mCtsNetUtils = new CtsNetUtils(mContext);
+        mCtsTetheringUtils = new CtsTetheringUtils(mContext);
         mTetherChangeReceiver = new TetherChangeReceiver();
         final IntentFilter filter = new IntentFilter(
                 TetheringManager.ACTION_TETHER_STATE_CHANGED);
@@ -138,40 +133,6 @@
         dropShellPermissionIdentity();
     }
 
-    private static class StopSoftApCallback implements SoftApCallback {
-        private final ConditionVariable mWaiting = new ConditionVariable();
-        @Override
-        public void onStateChanged(int state, int failureReason) {
-            if (state == WifiManager.WIFI_AP_STATE_DISABLED) mWaiting.open();
-        }
-
-        @Override
-        public void onConnectedClientsChanged(List<WifiClient> clients) { }
-
-        public void waitForSoftApStopped() {
-            if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
-                fail("stopSoftAp Timeout");
-            }
-        }
-    }
-
-    // Wait for softAp to be disabled. This is necessary on devices where stopping softAp
-    // deletes the interface. On these devices, tethering immediately stops when the softAp
-    // interface is removed, but softAp is not yet fully disabled. Wait for softAp to be
-    // fully disabled, because otherwise the next test might fail because it attempts to
-    // start softAp before it's fully stopped.
-    private void expectSoftApDisabled() {
-        final StopSoftApCallback callback = new StopSoftApCallback();
-        try {
-            mWm.registerSoftApCallback(c -> c.run(), callback);
-            // registerSoftApCallback will immediately call the callback with the current state, so
-            // this callback will fire even if softAp is already disabled.
-            callback.waitForSoftApStopped();
-        } finally {
-            mWm.unregisterSoftApCallback(callback);
-        }
-    }
-
     private class TetherChangeReceiver extends BroadcastReceiver {
         private class TetherState {
             final ArrayList<String> mAvailable;
@@ -241,77 +202,6 @@
         }
     }
 
-    private static class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
-        private static int TIMEOUT_MS = 30_000;
-        public static class CallbackValue {
-            public final int error;
-
-            private CallbackValue(final int e) {
-                error = e;
-            }
-
-            public static class OnTetheringStarted extends CallbackValue {
-                OnTetheringStarted() { super(TETHER_ERROR_NO_ERROR); }
-            }
-
-            public static class OnTetheringFailed extends CallbackValue {
-                OnTetheringFailed(final int error) { super(error); }
-            }
-
-            @Override
-            public String toString() {
-                return String.format("%s(%d)", getClass().getSimpleName(), error);
-            }
-        }
-
-        private final ArrayTrackRecord<CallbackValue>.ReadHead mHistory =
-                new ArrayTrackRecord<CallbackValue>().newReadHead();
-
-        @Override
-        public void onTetheringStarted() {
-            mHistory.add(new CallbackValue.OnTetheringStarted());
-        }
-
-        @Override
-        public void onTetheringFailed(final int error) {
-            mHistory.add(new CallbackValue.OnTetheringFailed(error));
-        }
-
-        public void verifyTetheringStarted() {
-            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
-            assertNotNull("No onTetheringStarted after " + TIMEOUT_MS + " ms", cv);
-            assertTrue("Fail start tethering:" + cv,
-                    cv instanceof CallbackValue.OnTetheringStarted);
-        }
-
-        public void expectTetheringFailed(final int expected) throws InterruptedException {
-            final CallbackValue cv = mHistory.poll(TIMEOUT_MS, c -> true);
-            assertNotNull("No onTetheringFailed after " + TIMEOUT_MS + " ms", cv);
-            assertTrue("Expect fail with error code " + expected + ", but received: " + cv,
-                (cv instanceof CallbackValue.OnTetheringFailed) && (cv.error == expected));
-        }
-    }
-
-    private static boolean isIfaceMatch(final List<String> ifaceRegexs,
-            final List<String> ifaces) {
-        return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
-    }
-
-    private static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
-        if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
-
-        if (ifaces == null) return false;
-
-        for (String s : ifaces) {
-            for (String regex : ifaceRegexs) {
-                if (s.matches(regex)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     @Test
     public void testStartTetheringWithStateChangeBroadcast() throws Exception {
         if (!mTM.isTetheringSupported()) return;
@@ -331,7 +221,7 @@
         mTetherChangeReceiver.expectTethering(true /* active */, wifiRegexs);
 
         mTM.stopTethering(TETHERING_WIFI);
-        expectSoftApDisabled();
+        mCtsTetheringUtils.expectSoftApDisabled();
         mTetherChangeReceiver.expectTethering(false /* active */, wifiRegexs);
     }
 
@@ -358,252 +248,24 @@
         assertFalse(tr2.getShouldShowEntitlementUi());
     }
 
-    // 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,
-            ON_TETHERABLE_REGEX,
-            ON_TETHERABLE_IFACES,
-            ON_TETHERED_IFACES,
-            ON_ERROR,
-            ON_CLIENTS,
-            ON_OFFLOAD_STATUS,
-        };
-
-        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 ArrayTrackRecord<CallbackValue> mHistory =
-                new ArrayTrackRecord<CallbackValue>();
-
-        private final ArrayTrackRecord<CallbackValue>.ReadHead mCurrent =
-                mHistory.newReadHead();
-
-        private TetheringInterfaceRegexps mTetherableRegex;
-        private List<String> mTetherableIfaces;
-        private List<String> mTetheredIfaces;
-
-        @Override
-        public void onTetheringSupported(boolean supported) {
-            mHistory.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, (supported ? 1 : 0)));
-        }
-
-        @Override
-        public void onUpstreamChanged(Network network) {
-            mHistory.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
-        }
-
-        @Override
-        public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) {
-            mTetherableRegex = reg;
-            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
-        }
-
-        @Override
-        public void onTetherableInterfacesChanged(List<String> interfaces) {
-            mTetherableIfaces = interfaces;
-            mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
-        }
-
-        @Override
-        public void onTetheredInterfacesChanged(List<String> interfaces) {
-            mTetheredIfaces = interfaces;
-            mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
-        }
-
-        @Override
-        public void onError(String ifName, int error) {
-            mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
-        }
-
-        @Override
-        public void onClientsChanged(Collection<TetheredClient> clients) {
-            mHistory.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
-        }
-
-        @Override
-        public void onOffloadStatusChanged(int status) {
-            mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
-        }
-
-        public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
-            assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
-                (cv) -> {
-                    if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
-                    final List<String> interfaces = (List<String>) cv.callbackParam;
-                    return isIfaceMatch(regexs, interfaces);
-                }));
-        }
-
-        public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
-            assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
-                (cv) -> {
-                    if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
-
-                    final List<String> interfaces = (List<String>) cv.callbackParam;
-
-                    // Null regexs means no active tethering.
-                    if (regexs == null) return interfaces.isEmpty();
-
-                    return isIfaceMatch(regexs, interfaces);
-                }));
-        }
-
-        public void expectCallbackStarted() {
-            int receivedBitMap = 0;
-            // The each bit represent a type from CallbackType.ON_*.
-            // Expect all of callbacks except for ON_ERROR.
-            final int expectedBitMap = 0xff ^ (1 << CallbackType.ON_ERROR.ordinal());
-            // 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);
-                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", mCurrent.poll(TIMEOUT_MS, (cv) -> {
-                if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) return false;
-
-                final int status = (int) cv.callbackParam;
-                for (int offloadStatus : offloadStatuses) if (offloadStatus == status) return true;
-
-                return false;
-            }));
-        }
-
-        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<String>) 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<CallbackValue>.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;
-        }
-
-        public List<String> getTetherableInterfaces() {
-            return mTetherableIfaces;
-        }
-
-        public List<String> getTetheredInterfaces() {
-            return mTetheredIfaces;
-        }
-    }
-
-    private TestTetheringEventCallback registerTetheringEventCallback() {
-        final TestTetheringEventCallback tetherEventCallback =
-                new TestTetheringEventCallback();
-
-        mTM.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
-        tetherEventCallback.expectCallbackStarted();
-
-        return tetherEventCallback;
-    }
-
-    private void unregisterTetheringEventCallback(final TestTetheringEventCallback callback) {
-        mTM.unregisterTetheringEventCallback(callback);
-    }
-
-    private List<String> getWifiTetherableInterfaceRegexps(
-            final TestTetheringEventCallback callback) {
-        return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
-    }
-
-    private boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
-        return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
-    }
-
-    private void startWifiTethering(final TestTetheringEventCallback callback)
-            throws InterruptedException {
-        final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
-        assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
-
-        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
-        final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
-                .setShouldShowEntitlementUi(false).build();
-        mTM.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
-        startTetheringCallback.verifyTetheringStarted();
-
-        callback.expectTetheredInterfacesChanged(wifiRegexs);
-
-        callback.expectOneOfOffloadStatusChanged(
-                TETHER_HARDWARE_OFFLOAD_STARTED,
-                TETHER_HARDWARE_OFFLOAD_FAILED);
-    }
-
-    private void stopWifiTethering(final TestTetheringEventCallback callback) {
-        mTM.stopTethering(TETHERING_WIFI);
-        expectSoftApDisabled();
-        callback.expectTetheredInterfacesChanged(null);
-        callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
-    }
-
     @Test
     public void testRegisterTetheringEventCallback() throws Exception {
-        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
         tetherEventCallback.assumeTetheringSupported();
 
         if (!isWifiTetheringSupported(tetherEventCallback)) {
-            unregisterTetheringEventCallback(tetherEventCallback);
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
             return;
         }
 
-        startWifiTethering(tetherEventCallback);
+        mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
         final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
         assertEquals(1, tetheredIfaces.size());
         final String wifiTetheringIface = tetheredIfaces.get(0);
 
-        stopWifiTethering(tetherEventCallback);
+        mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
 
         try {
             final int ret = mTM.tether(wifiTetheringIface);
@@ -617,13 +279,14 @@
             tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
         } finally {
             mTM.untether(wifiTetheringIface);
-            unregisterTetheringEventCallback(tetherEventCallback);
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
         }
     }
 
     @Test
     public void testGetTetherableInterfaceRegexps() {
-        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
         tetherEventCallback.assumeTetheringSupported();
 
         final TetheringInterfaceRegexps tetherableRegexs =
@@ -641,12 +304,13 @@
         wifiRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
         usbRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
 
-        unregisterTetheringEventCallback(tetherEventCallback);
+        mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
     }
 
     @Test
     public void testStopAllTethering() throws Exception {
-        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
         tetherEventCallback.assumeTetheringSupported();
 
         try {
@@ -655,12 +319,12 @@
             // TODO: start ethernet tethering here when TetheringManagerTest is moved to
             // TetheringIntegrationTest.
 
-            startWifiTethering(tetherEventCallback);
+            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
             mTM.stopAllTethering();
             tetherEventCallback.expectTetheredInterfacesChanged(null);
         } finally {
-            unregisterTetheringEventCallback(tetherEventCallback);
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
         }
     }
 
@@ -749,7 +413,8 @@
     @Test
     public void testTetheringUpstream() throws Exception {
         assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-        final TestTetheringEventCallback tetherEventCallback = registerTetheringEventCallback();
+        final TestTetheringEventCallback tetherEventCallback =
+                mCtsTetheringUtils.registerTetheringEventCallback();
         tetherEventCallback.assumeTetheringSupported();
         final boolean previousWifiEnabledState = mWm.isWifiEnabled();
 
@@ -778,7 +443,7 @@
 
             assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
 
-            startWifiTethering(tetherEventCallback);
+            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
             final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                     Context.TELEPHONY_SERVICE);
@@ -789,9 +454,9 @@
             assertTrue(netCap.hasTransport(TRANSPORT_CELLULAR));
             assertTrue(netCap.hasCapability(expectedCap));
 
-            stopWifiTethering(tetherEventCallback);
+            mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
         } finally {
-            unregisterTetheringEventCallback(tetherEventCallback);
+            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
             if (previousWifiEnabledState) {
                 mCtsNetUtils.connectToWifi();
             }