Fix ConnectivityManagerApi23Test failures and remove duplication.

1. All ConnectivityManagerApi23Test were failed due to
   WifiManager#setWifiEnabled doesn't allow to use since
   Android Q. So we need to use shell command to enable/disable
   Wi-Fi instead.
2. Some methods are duplicated between
   ConnectivityManagerApi23Test and ConnectivityManagerTest, but
   they are not identical. So put these methods into
   ConnectivityUtils to clean up duplications and prevent fork
   happened again.

Bug: 133334943
Bug: 133209319
Test: Run the below tests on Crosshatch, Sailfish, Bonito.
      atest CtsNetApi23TestCases
      atest CtsNetTestCases

Change-Id: Ic37111cb12a46f5c36c2be887250c5d762216f6e
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 779d5c4..b6ea4af 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -42,6 +42,7 @@
         "FrameworksNetCommonTests",
         "core-tests-support",
         "compatibility-device-util-axt",
+        "cts-net-utils",
         "ctstestrunner-axt",
         "ctstestserver",
         "mockwebserver",
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 48161cf..ffe854e 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -31,6 +31,7 @@
     static_libs: [
         "core-tests-support",
         "compatibility-device-util-axt",
+        "cts-net-utils",
         "ctstestrunner-axt",
         "ctstestserver",
         "mockwebserver",
diff --git a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
index f38490e..cdb66e3 100644
--- a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -25,58 +25,33 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.State;
-import android.net.NetworkRequest;
-import android.net.wifi.WifiManager;
+import android.net.cts.util.CtsNetUtils;
 import android.os.Looper;
-import android.system.Os;
-import android.system.OsConstants;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 public class ConnectivityManagerApi23Test extends AndroidTestCase {
     private static final String TAG = ConnectivityManagerApi23Test.class.getSimpleName();
-
-    private static final String TEST_HOST = "connectivitycheck.gstatic.com";
-    private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int SEND_BROADCAST_TIMEOUT = 30000;
-    private static final int HTTP_PORT = 80;
     // Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
     public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
             "android.net.cts.appForApi23.getWifiConnectivityActionCount";
     // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
-    private static final String NETWORK_CALLBACK_ACTION =
-            "ConnectivityManagerTest.NetworkCallbackAction";
-    private static final String HTTP_REQUEST =
-            "GET /generate_204 HTTP/1.0\r\n" +
-                    "Host: " + TEST_HOST + "\r\n" +
-                    "Connection: keep-alive\r\n\r\n";
 
     private Context mContext;
-    private ConnectivityManager mCm;
-    private WifiManager mWifiManager;
     private PackageManager mPackageManager;
+    private CtsNetUtils mCtsNetUtils;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         Looper.prepare();
         mContext = getContext();
-        mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mPackageManager = mContext.getPackageManager();
+        mCtsNetUtils = new CtsNetUtils(mContext);
     }
 
     /**
@@ -89,7 +64,7 @@
         }
         ConnectivityReceiver.prepare();
 
-        toggleWifi();
+        mCtsNetUtils.toggleWifi();
 
         // The connectivity broadcast has been sent; push through a terminal broadcast
         // to wait for in the receive to confirm it didn't see the connectivity change.
@@ -112,7 +87,7 @@
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
         Thread.sleep(200);
 
-        toggleWifi();
+        mCtsNetUtils.toggleWifi();
 
         Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
         assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
@@ -130,7 +105,7 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         mContext.registerReceiver(receiver, filter);
 
-        toggleWifi();
+        mCtsNetUtils.toggleWifi();
         Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
         finalIntent.setClass(mContext, ConnectivityReceiver.class);
         mContext.sendBroadcast(finalIntent);
@@ -138,19 +113,6 @@
         assertTrue(ConnectivityReceiver.waitForBroadcast());
     }
 
-    // Toggle WiFi twice, leaving it in the state it started in
-    private void toggleWifi() {
-        if (mWifiManager.isWifiEnabled()) {
-            Network wifiNetwork = getWifiNetwork();
-            disconnectFromWifi(wifiNetwork);
-            connectToWifi();
-        } else {
-            connectToWifi();
-            Network wifiNetwork = getWifiNetwork();
-            disconnectFromWifi(wifiNetwork);
-        }
-    }
-
     private int sendOrderedBroadcastAndReturnResultCode(
             Intent intent, int timeoutMs) throws InterruptedException {
         final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
@@ -167,233 +129,4 @@
         return resultCode;
     }
 
-    private Network getWifiNetwork() {
-        TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network network = null;
-        try {
-            network = callback.waitForAvailable();
-        } catch (InterruptedException e) {
-            fail("NetworkCallback wait was interrupted.");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-        }
-        assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
-        return network;
-    }
-
-    /** Disable WiFi and wait for it to become disconnected from the network. */
-    private void disconnectFromWifi(Network wifiNetworkToCheck) {
-        final TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network lostWifiNetwork = null;
-
-        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        // Assert that we can establish a TCP connection on wifi.
-        Socket wifiBoundSocket = null;
-        if (wifiNetworkToCheck != null) {
-            try {
-                wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
-                testHttpRequest(wifiBoundSocket);
-            } catch (IOException e) {
-                fail("HTTP request before wifi disconnected failed with: " + e);
-            }
-        }
-
-        boolean disconnected = false;
-        try {
-            assertTrue(mWifiManager.setWifiEnabled(false));
-            // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
-            lostWifiNetwork = callback.waitForLost();
-            assertNotNull(lostWifiNetwork);
-            disconnected = receiver.waitForState();
-        } catch (InterruptedException ex) {
-            fail("disconnectFromWifi was interrupted");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-            mContext.unregisterReceiver(receiver);
-        }
-
-        assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
-
-        // Check that the socket is closed when wifi disconnects.
-        if (wifiBoundSocket != null) {
-            try {
-                testHttpRequest(wifiBoundSocket);
-                fail("HTTP request should not succeed after wifi disconnects");
-            } catch (IOException expected) {
-                assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
-            }
-        }
-    }
-
-    /** Enable WiFi and wait for it to become connected to a network. */
-    private Network connectToWifi() {
-        final TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network wifiNetwork = null;
-
-        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        boolean connected = false;
-        try {
-            assertTrue(mWifiManager.setWifiEnabled(true));
-            // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
-            wifiNetwork = callback.waitForAvailable();
-            assertNotNull(wifiNetwork);
-            connected = receiver.waitForState();
-        } catch (InterruptedException ex) {
-            fail("connectToWifi was interrupted");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-            mContext.unregisterReceiver(receiver);
-        }
-
-        assertTrue("Wifi must be configured to connect to an access point for this test.",
-                connected);
-        return wifiNetwork;
-    }
-
-    private NetworkRequest makeWifiNetworkRequest() {
-        return new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-                .build();
-    }
-
-    private void testHttpRequest(Socket s) throws IOException {
-        OutputStream out = s.getOutputStream();
-        InputStream in = s.getInputStream();
-
-        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
-        byte[] responseBytes = new byte[4096];
-        out.write(requestBytes);
-        in.read(responseBytes);
-        assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
-    }
-
-    private Socket getBoundSocket(Network network, String host, int port) throws IOException {
-        InetSocketAddress addr = new InetSocketAddress(host, port);
-        Socket s = network.getSocketFactory().createSocket();
-        try {
-            s.setSoTimeout(SOCKET_TIMEOUT_MS);
-            s.connect(addr, SOCKET_TIMEOUT_MS);
-        } catch (IOException e) {
-            s.close();
-            throw e;
-        }
-        return s;
-    }
-
-    /**
-     * Receiver that captures the last connectivity change's network type and state. Recognizes
-     * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
-     */
-    private class ConnectivityActionReceiver extends BroadcastReceiver {
-
-        private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
-
-        private final int mNetworkType;
-        private final NetworkInfo.State mNetState;
-
-        ConnectivityActionReceiver(int networkType, NetworkInfo.State netState) {
-            mNetworkType = networkType;
-            mNetState = netState;
-        }
-
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            NetworkInfo networkInfo = null;
-
-            // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
-            // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
-            // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
-            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
-                networkInfo = intent.getExtras()
-                        .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
-                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO", networkInfo);
-            } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
-                Network network = intent.getExtras()
-                        .getParcelable(ConnectivityManager.EXTRA_NETWORK);
-                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
-                networkInfo = mCm.getNetworkInfo(network);
-                if (networkInfo == null) {
-                    // When disconnecting, it seems like we get an intent sent with an invalid
-                    // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
-                    // it is invalid. Ignore these.
-                    Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
-                            + "invalid network");
-                    return;
-                }
-            } else {
-                fail("ConnectivityActionReceiver received unxpected intent action: " + action);
-            }
-
-            assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
-            int networkType = networkInfo.getType();
-            State networkState = networkInfo.getState();
-            Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
-            if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
-                mReceiveLatch.countDown();
-            }
-        }
-
-        public boolean waitForState() throws InterruptedException {
-            return mReceiveLatch.await(30, TimeUnit.SECONDS);
-        }
-    }
-
-    /**
-     * Callback used in testRegisterNetworkCallback that allows caller to block on
-     * {@code onAvailable}.
-     */
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
-        private final CountDownLatch mLostLatch = new CountDownLatch(1);
-        private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
-
-        public Network currentNetwork;
-        public Network lastLostNetwork;
-
-        public Network waitForAvailable() throws InterruptedException {
-            return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
-        }
-
-        public Network waitForLost() throws InterruptedException {
-            return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
-        }
-
-        public boolean waitForUnavailable() throws InterruptedException {
-            return mUnavailableLatch.await(2, TimeUnit.SECONDS);
-        }
-
-
-        @Override
-        public void onAvailable(Network network) {
-            currentNetwork = network;
-            mAvailableLatch.countDown();
-        }
-
-        @Override
-        public void onLost(Network network) {
-            lastLostNetwork = network;
-            if (network.equals(currentNetwork)) {
-                currentNetwork = null;
-            }
-            mLostLatch.countDown();
-        }
-
-        @Override
-        public void onUnavailable() {
-            mUnavailableLatch.countDown();
-        }
-    }
 }
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index c444445..c3ae793 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -21,8 +21,12 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
+import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
+import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
+import static android.net.cts.util.CtsNetUtils.TEST_HOST;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
 import static android.system.OsConstants.AF_INET;
@@ -34,7 +38,6 @@
 import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.UiAutomation;
-import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -53,6 +56,7 @@
 import android.net.NetworkInfo.State;
 import android.net.NetworkRequest;
 import android.net.SocketKeepalive;
+import android.net.cts.util.CtsNetUtils;
 import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
@@ -61,15 +65,12 @@
 import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
-import android.system.Os;
-import android.system.OsConstants;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.SystemUtil;
 import com.android.internal.R;
 import com.android.internal.telephony.PhoneConstants;
 
@@ -107,8 +108,6 @@
     public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
 
     private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
-    private static final String TEST_HOST = "connectivitycheck.gstatic.com";
-    private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int CONNECT_TIMEOUT_MS = 2000;
     private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
     private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
@@ -116,16 +115,6 @@
     private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
     private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
-    private static final int HTTP_PORT = 80;
-    private static final String HTTP_REQUEST =
-            "GET /generate_204 HTTP/1.0\r\n" +
-            "Host: " + TEST_HOST + "\r\n" +
-            "Connection: keep-alive\r\n\r\n";
-
-    // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
-    private static final String NETWORK_CALLBACK_ACTION =
-            "ConnectivityManagerTest.NetworkCallbackAction";
-
     // device could have only one interface: data, wifi.
     private static final int MIN_NUM_NETWORK_TYPES = 1;
 
@@ -137,8 +126,8 @@
     private final HashMap<Integer, NetworkConfig> mNetworks =
             new HashMap<Integer, NetworkConfig>();
     boolean mWifiConnectAttempted;
-    private TestNetworkCallback mCellNetworkCallback;
     private UiAutomation mUiAutomation;
+    private CtsNetUtils mCtsNetUtils;
     private boolean mShellPermissionIdentityAdopted;
 
     @Override
@@ -150,6 +139,7 @@
         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
         mPackageManager = mContext.getPackageManager();
+        mCtsNetUtils = new CtsNetUtils(mContext);
         mWifiConnectAttempted = false;
 
         // Get com.android.internal.R.array.networkAttributes
@@ -174,10 +164,10 @@
     protected void tearDown() throws Exception {
         // Return WiFi to its original disabled state after tests that explicitly connect.
         if (mWifiConnectAttempted) {
-            disconnectFromWifi(null);
+            mCtsNetUtils.disconnectFromWifi(null);
         }
-        if (cellConnectAttempted()) {
-            disconnectFromCell();
+        if (mCtsNetUtils.cellConnectAttempted()) {
+            mCtsNetUtils.disconnectFromCell();
         }
         dropShellPermissionIdentity();
         super.tearDown();
@@ -190,10 +180,10 @@
      */
     private Network ensureWifiConnected() {
         if (mWifiManager.isWifiEnabled()) {
-            return getWifiNetwork();
+            return mCtsNetUtils.getWifiNetwork();
         }
         mWifiConnectAttempted = true;
-        return connectToWifi();
+        return mCtsNetUtils.connectToWifi();
     }
 
     public void testIsNetworkTypeValid() {
@@ -301,8 +291,8 @@
             return;
         }
 
-        Network wifiNetwork = connectToWifi();
-        Network cellNetwork = connectToCell();
+        Network wifiNetwork = mCtsNetUtils.connectToWifi();
+        Network cellNetwork = mCtsNetUtils.connectToCell();
         // This server returns the requestor's IP address as the response body.
         URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
         String wifiAddressString = httpGet(wifiNetwork, url);
@@ -320,33 +310,6 @@
         assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
     }
 
-    private Network connectToCell() throws InterruptedException {
-        if (cellConnectAttempted()) {
-            throw new IllegalStateException("Already connected");
-        }
-        NetworkRequest cellRequest = new NetworkRequest.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .build();
-        mCellNetworkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(cellRequest, mCellNetworkCallback);
-        final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
-        assertNotNull("Cell network not available within timeout", cellNetwork);
-        return cellNetwork;
-    }
-
-    private boolean cellConnectAttempted() {
-        return mCellNetworkCallback != null;
-    }
-
-    private void disconnectFromCell() {
-        if (!cellConnectAttempted()) {
-            throw new IllegalStateException("Cell connection not attempted");
-        }
-        mCm.unregisterNetworkCallback(mCellNetworkCallback);
-        mCellNetworkCallback = null;
-    }
-
     /**
      * Performs a HTTP GET to the specified URL on the specified Network, and returns
      * the response body decoded as UTF-8.
@@ -508,7 +471,7 @@
         filter.addAction(NETWORK_CALLBACK_ACTION);
 
         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
+                mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
         mContext.registerReceiver(receiver, filter);
 
         // Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
@@ -567,7 +530,7 @@
     public void testRequestNetworkCallback_onUnavailable() {
         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
         if (previousWifiEnabledState) {
-            disconnectFromWifi(null);
+            mCtsNetUtils.disconnectFromWifi(null);
         }
 
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -584,42 +547,11 @@
         } finally {
             mCm.unregisterNetworkCallback(callback);
             if (previousWifiEnabledState) {
-                connectToWifi();
+                mCtsNetUtils.connectToWifi();
             }
         }
     }
 
-    /** Enable WiFi and wait for it to become connected to a network. */
-    private Network connectToWifi() {
-        final TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network wifiNetwork = null;
-
-        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        boolean connected = false;
-        try {
-            SystemUtil.runShellCommand("svc wifi enable");
-            // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
-            wifiNetwork = callback.waitForAvailable();
-            assertNotNull(wifiNetwork);
-            connected = receiver.waitForState();
-        } catch (InterruptedException ex) {
-            fail("connectToWifi was interrupted");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-            mContext.unregisterReceiver(receiver);
-        }
-
-        assertTrue("Wifi must be configured to connect to an access point for this test.",
-                connected);
-        return wifiNetwork;
-    }
-
     private InetAddress getFirstV4Address(Network network) {
         LinkProperties linkProperties = mCm.getLinkProperties(network);
         for (InetAddress address : linkProperties.getAddresses()) {
@@ -630,199 +562,6 @@
         return null;
     }
 
-    private Socket getBoundSocket(Network network, String host, int port) throws IOException {
-        InetSocketAddress addr = new InetSocketAddress(host, port);
-        Socket s = network.getSocketFactory().createSocket();
-        try {
-            s.setSoTimeout(SOCKET_TIMEOUT_MS);
-            s.connect(addr, SOCKET_TIMEOUT_MS);
-        } catch (IOException e) {
-            s.close();
-            throw e;
-        }
-        return s;
-    }
-
-    private void testHttpRequest(Socket s) throws IOException {
-        OutputStream out = s.getOutputStream();
-        InputStream in = s.getInputStream();
-
-        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
-        byte[] responseBytes = new byte[4096];
-        out.write(requestBytes);
-        in.read(responseBytes);
-        assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
-    }
-
-    /** Disable WiFi and wait for it to become disconnected from the network. */
-    private void disconnectFromWifi(Network wifiNetworkToCheck) {
-        final TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network lostWifiNetwork = null;
-
-        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
-                ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        // Assert that we can establish a TCP connection on wifi.
-        Socket wifiBoundSocket = null;
-        if (wifiNetworkToCheck != null) {
-            try {
-                wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
-                testHttpRequest(wifiBoundSocket);
-            } catch (IOException e) {
-                fail("HTTP request before wifi disconnected failed with: " + e);
-            }
-        }
-
-        boolean disconnected = false;
-        try {
-            SystemUtil.runShellCommand("svc wifi disable");
-            // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
-            lostWifiNetwork = callback.waitForLost();
-            assertNotNull(lostWifiNetwork);
-            disconnected = receiver.waitForState();
-        } catch (InterruptedException ex) {
-            fail("disconnectFromWifi was interrupted");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-            mContext.unregisterReceiver(receiver);
-        }
-
-        assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
-
-        // Check that the socket is closed when wifi disconnects.
-        if (wifiBoundSocket != null) {
-            try {
-                testHttpRequest(wifiBoundSocket);
-                fail("HTTP request should not succeed after wifi disconnects");
-            } catch (IOException expected) {
-                assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
-            }
-        }
-    }
-
-    /**
-     * Receiver that captures the last connectivity change's network type and state. Recognizes
-     * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
-     */
-    private class ConnectivityActionReceiver extends BroadcastReceiver {
-
-        private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
-
-        private final int mNetworkType;
-        private final NetworkInfo.State mNetState;
-
-        ConnectivityActionReceiver(int networkType, NetworkInfo.State netState) {
-            mNetworkType = networkType;
-            mNetState = netState;
-        }
-
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            NetworkInfo networkInfo = null;
-
-            // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
-            // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
-            // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
-            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
-                networkInfo = intent.getExtras()
-                        .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
-                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO", networkInfo);
-            } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
-                Network network = intent.getExtras()
-                        .getParcelable(ConnectivityManager.EXTRA_NETWORK);
-                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
-                networkInfo = mCm.getNetworkInfo(network);
-                if (networkInfo == null) {
-                    // When disconnecting, it seems like we get an intent sent with an invalid
-                    // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
-                    // it is invalid. Ignore these.
-                    Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
-                            + "invalid network");
-                    return;
-                }
-            } else {
-                fail("ConnectivityActionReceiver received unxpected intent action: " + action);
-            }
-
-            assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
-            int networkType = networkInfo.getType();
-            State networkState = networkInfo.getState();
-            Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
-            if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
-                mReceiveLatch.countDown();
-            }
-        }
-
-        public boolean waitForState() throws InterruptedException {
-            return mReceiveLatch.await(30, TimeUnit.SECONDS);
-        }
-    }
-
-    /**
-     * Callback used in testRegisterNetworkCallback that allows caller to block on
-     * {@code onAvailable}.
-     */
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
-        private final CountDownLatch mLostLatch = new CountDownLatch(1);
-        private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
-
-        public Network currentNetwork;
-        public Network lastLostNetwork;
-
-        public Network waitForAvailable() throws InterruptedException {
-            return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
-        }
-
-        public Network waitForLost() throws InterruptedException {
-            return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
-        }
-
-        public boolean waitForUnavailable() throws InterruptedException {
-            return mUnavailableLatch.await(2, TimeUnit.SECONDS);
-        }
-
-
-        @Override
-        public void onAvailable(Network network) {
-            currentNetwork = network;
-            mAvailableLatch.countDown();
-        }
-
-        @Override
-        public void onLost(Network network) {
-            lastLostNetwork = network;
-            if (network.equals(currentNetwork)) {
-                currentNetwork = null;
-            }
-            mLostLatch.countDown();
-        }
-
-        @Override
-        public void onUnavailable() {
-            mUnavailableLatch.countDown();
-        }
-    }
-
-    private Network getWifiNetwork() {
-        TestNetworkCallback callback = new TestNetworkCallback();
-        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
-        Network network = null;
-        try {
-            network = callback.waitForAvailable();
-        } catch (InterruptedException e) {
-            fail("NetworkCallback wait was interrupted.");
-        } finally {
-            mCm.unregisterNetworkCallback(callback);
-        }
-        assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
-        return network;
-    }
-
     /** Verify restricted networks cannot be requested. */
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testRestrictedNetworks() {
@@ -1151,7 +890,7 @@
         if (!isKeepaliveSupported()) return;
 
         final Network network = ensureWifiConnected();
-        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+        final byte[] requestBytes = CtsNetUtils.HTTP_REQUEST.getBytes("UTF-8");
         // So far only ipv4 tcp keepalive offload is supported.
         // TODO: add test case for ipv6 tcp keepalive offload when it is supported.
         try (Socket s = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
new file mode 100644
index 0000000..1f94613
--- /dev/null
+++ b/tests/cts/net/util/Android.bp
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// Common utilities for cts net tests.
+java_library {
+    name: "cts-net-utils",
+    srcs: ["java/**/*.java", "java/**/*.kt"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "junit",
+    ],
+}
\ No newline at end of file
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
new file mode 100644
index 0000000..e19d2ba
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2019 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.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiManager;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public final class CtsNetUtils {
+    private static final String TAG = CtsNetUtils.class.getSimpleName();
+    private static final int DURATION = 10000;
+    private static final int SOCKET_TIMEOUT_MS = 2000;
+
+    public static final int HTTP_PORT = 80;
+    public static final String TEST_HOST = "connectivitycheck.gstatic.com";
+    public static final String HTTP_REQUEST =
+            "GET /generate_204 HTTP/1.0\r\n" +
+                    "Host: " + TEST_HOST + "\r\n" +
+                    "Connection: keep-alive\r\n\r\n";
+    // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
+    public static final String NETWORK_CALLBACK_ACTION =
+            "ConnectivityManagerTest.NetworkCallbackAction";
+
+    private Context mContext;
+    private ConnectivityManager mCm;
+    private WifiManager mWifiManager;
+    private TestNetworkCallback mCellNetworkCallback;
+
+    public CtsNetUtils(Context context) {
+        mContext = context;
+        mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    // Toggle WiFi twice, leaving it in the state it started in
+    public void toggleWifi() {
+        if (mWifiManager.isWifiEnabled()) {
+            Network wifiNetwork = getWifiNetwork();
+            disconnectFromWifi(wifiNetwork);
+            connectToWifi();
+        } else {
+            connectToWifi();
+            Network wifiNetwork = getWifiNetwork();
+            disconnectFromWifi(wifiNetwork);
+        }
+    }
+
+    /** Enable WiFi and wait for it to become connected to a network. */
+    public Network connectToWifi() {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network wifiNetwork = null;
+
+        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
+                mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(receiver, filter);
+
+        boolean connected = false;
+        try {
+            SystemUtil.runShellCommand("svc wifi enable");
+            // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
+            wifiNetwork = callback.waitForAvailable();
+            assertNotNull(wifiNetwork);
+            connected = receiver.waitForState();
+        } catch (InterruptedException ex) {
+            fail("connectToWifi was interrupted");
+        } finally {
+            mCm.unregisterNetworkCallback(callback);
+            mContext.unregisterReceiver(receiver);
+        }
+
+        assertTrue("Wifi must be configured to connect to an access point for this test.",
+                connected);
+        return wifiNetwork;
+    }
+
+    /** Disable WiFi and wait for it to become disconnected from the network. */
+    public void disconnectFromWifi(Network wifiNetworkToCheck) {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network lostWifiNetwork = null;
+
+        ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
+                mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(receiver, filter);
+
+        // Assert that we can establish a TCP connection on wifi.
+        Socket wifiBoundSocket = null;
+        if (wifiNetworkToCheck != null) {
+            try {
+                wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
+                testHttpRequest(wifiBoundSocket);
+            } catch (IOException e) {
+                fail("HTTP request before wifi disconnected failed with: " + e);
+            }
+        }
+
+        boolean disconnected = false;
+        try {
+            SystemUtil.runShellCommand("svc wifi disable");
+            // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
+            lostWifiNetwork = callback.waitForLost();
+            assertNotNull(lostWifiNetwork);
+            disconnected = receiver.waitForState();
+        } catch (InterruptedException ex) {
+            fail("disconnectFromWifi was interrupted");
+        } finally {
+            mCm.unregisterNetworkCallback(callback);
+            mContext.unregisterReceiver(receiver);
+        }
+
+        assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
+
+        // Check that the socket is closed when wifi disconnects.
+        if (wifiBoundSocket != null) {
+            try {
+                testHttpRequest(wifiBoundSocket);
+                fail("HTTP request should not succeed after wifi disconnects");
+            } catch (IOException expected) {
+                assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
+            }
+        }
+    }
+
+    public Network getWifiNetwork() {
+        TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+        Network network = null;
+        try {
+            network = callback.waitForAvailable();
+        } catch (InterruptedException e) {
+            fail("NetworkCallback wait was interrupted.");
+        } finally {
+            mCm.unregisterNetworkCallback(callback);
+        }
+        assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
+        return network;
+    }
+
+    public Network connectToCell() throws InterruptedException {
+        if (cellConnectAttempted()) {
+            throw new IllegalStateException("Already connected");
+        }
+        NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build();
+        mCellNetworkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(cellRequest, mCellNetworkCallback);
+        final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
+        assertNotNull("Cell network not available. " +
+                "Please ensure the device has working mobile data.", cellNetwork);
+        return cellNetwork;
+    }
+
+    public void disconnectFromCell() {
+        if (!cellConnectAttempted()) {
+            throw new IllegalStateException("Cell connection not attempted");
+        }
+        mCm.unregisterNetworkCallback(mCellNetworkCallback);
+        mCellNetworkCallback = null;
+    }
+
+    public boolean cellConnectAttempted() {
+        return mCellNetworkCallback != null;
+    }
+
+    private NetworkRequest makeWifiNetworkRequest() {
+        return new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+    }
+
+    private void testHttpRequest(Socket s) throws IOException {
+        OutputStream out = s.getOutputStream();
+        InputStream in = s.getInputStream();
+
+        final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+        byte[] responseBytes = new byte[4096];
+        out.write(requestBytes);
+        in.read(responseBytes);
+        assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
+    }
+
+    private Socket getBoundSocket(Network network, String host, int port) throws IOException {
+        InetSocketAddress addr = new InetSocketAddress(host, port);
+        Socket s = network.getSocketFactory().createSocket();
+        try {
+            s.setSoTimeout(SOCKET_TIMEOUT_MS);
+            s.connect(addr, SOCKET_TIMEOUT_MS);
+        } catch (IOException e) {
+            s.close();
+            throw e;
+        }
+        return s;
+    }
+
+    /**
+     * Receiver that captures the last connectivity change's network type and state. Recognizes
+     * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
+     */
+    public static class ConnectivityActionReceiver extends BroadcastReceiver {
+
+        private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
+
+        private final int mNetworkType;
+        private final NetworkInfo.State mNetState;
+        private final ConnectivityManager mCm;
+
+        public ConnectivityActionReceiver(ConnectivityManager cm, int networkType,
+                NetworkInfo.State netState) {
+            this.mCm = cm;
+            mNetworkType = networkType;
+            mNetState = netState;
+        }
+
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            NetworkInfo networkInfo = null;
+
+            // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
+            // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
+            // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
+            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
+                networkInfo = intent.getExtras()
+                        .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
+                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO",
+                        networkInfo);
+            } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
+                Network network = intent.getExtras()
+                        .getParcelable(ConnectivityManager.EXTRA_NETWORK);
+                assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
+                networkInfo = this.mCm.getNetworkInfo(network);
+                if (networkInfo == null) {
+                    // When disconnecting, it seems like we get an intent sent with an invalid
+                    // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
+                    // it is invalid. Ignore these.
+                    Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
+                            + "invalid network");
+                    return;
+                }
+            } else {
+                fail("ConnectivityActionReceiver received unxpected intent action: " + action);
+            }
+
+            assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
+            int networkType = networkInfo.getType();
+            State networkState = networkInfo.getState();
+            Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
+            if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
+                mReceiveLatch.countDown();
+            }
+        }
+
+        public boolean waitForState() throws InterruptedException {
+            return mReceiveLatch.await(30, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Callback used in testRegisterNetworkCallback that allows caller to block on
+     * {@code onAvailable}.
+     */
+    public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
+        private final CountDownLatch mLostLatch = new CountDownLatch(1);
+        private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
+
+        public Network currentNetwork;
+        public Network lastLostNetwork;
+
+        public Network waitForAvailable() throws InterruptedException {
+            return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
+        }
+
+        public Network waitForLost() throws InterruptedException {
+            return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
+        }
+
+        public boolean waitForUnavailable() throws InterruptedException {
+            return mUnavailableLatch.await(2, TimeUnit.SECONDS);
+        }
+
+
+        @Override
+        public void onAvailable(Network network) {
+            currentNetwork = network;
+            mAvailableLatch.countDown();
+        }
+
+        @Override
+        public void onLost(Network network) {
+            lastLostNetwork = network;
+            if (network.equals(currentNetwork)) {
+                currentNetwork = null;
+            }
+            mLostLatch.countDown();
+        }
+
+        @Override
+        public void onUnavailable() {
+            mUnavailableLatch.countDown();
+        }
+    }
+}