Merge "Minor changes for DnsResolverTest" into qt-dev
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 0656cae..c7cab7b 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -16,6 +16,9 @@
 <configuration description="Config for CTS net host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
     <target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 4e4d1f6..6dfa2f3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -153,7 +153,14 @@
         Log.i(TAG, "Apps status on " + getName() + ":\n"
                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
                 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
-        executeShellCommand("settings get global app_idle_constants");
+
+        // app_idle_constants set in NetPolicyTestsPreparer.setUp() is not always sucessful (suspect
+        // timing issue), here we set it again to make sure.
+        final String appIdleConstants = "parole_duration=0,stable_charging_threshold=0";
+        executeShellCommand("settings put global app_idle_constants " + appIdleConstants);
+        final String currentConstants =
+                executeShellCommand("settings get global app_idle_constants");
+        assertEquals(appIdleConstants, currentConstants);
    }
 
     @Override
@@ -204,7 +211,7 @@
         do {
             attempts++;
             count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
-            if (count == expectedCount) {
+            if (count >= expectedCount) {
                 break;
             }
             Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 72563d4..cfe6a73 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -22,6 +22,8 @@
 
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
+
 public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
 
     private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
@@ -35,7 +37,6 @@
         super.setUp();
 
         mIsDataSaverSupported = isDataSaverSupported();
-        if (!isSupported()) return;
 
         // Set initial state.
         setRestrictBackground(false);
@@ -201,6 +202,20 @@
         }
     }
 
+    @CddTest(requirement="7.4.7/C-2-2")
+    public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
+        if (isSupported()) return;
+
+        setRestrictBackground(true);
+        assertRestrictBackgroundChangedReceived(0);
+
+        setRestrictBackground(false);
+        assertRestrictBackgroundChangedReceived(0);
+
+        setRestrictBackground(true);
+        assertRestrictBackgroundChangedReceived(0);
+    }
+
     private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
         assertRestrictBackgroundStatus(expectedStatus);
         assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index 7d91574..7d3d4fc 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -17,6 +17,7 @@
 package com.android.cts.net.hostside;
 
 import android.content.Intent;
+import android.net.Network;
 import android.net.ProxyInfo;
 import android.net.VpnService;
 import android.os.ParcelFileDescriptor;
@@ -27,6 +28,7 @@
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 
 public class MyVpnService extends VpnService {
 
@@ -118,6 +120,18 @@
             }
         }
 
+        ArrayList<Network> underlyingNetworks =
+                intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
+        if (underlyingNetworks == null) {
+            // VPN tracks default network
+            builder.setUnderlyingNetworks(null);
+        } else {
+            builder.setUnderlyingNetworks(underlyingNetworks.toArray(new Network[0]));
+        }
+
+        boolean isAlwaysMetered = intent.getBooleanExtra(packageName + ".isAlwaysMetered", false);
+        builder.setMetered(isAlwaysMetered);
+
         ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy");
         builder.setHttpProxy(vpnProxy);
         builder.setMtu(MTU);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 17e1347..2fc85f6 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -19,6 +19,7 @@
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.*;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -31,6 +32,7 @@
 import android.net.Proxy;
 import android.net.ProxyInfo;
 import android.net.VpnService;
+import android.net.wifi.WifiManager;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.SystemProperties;
@@ -61,7 +63,9 @@
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Random;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -100,6 +104,7 @@
     private MyActivity mActivity;
     private String mPackageName;
     private ConnectivityManager mCM;
+    private WifiManager mWifiManager;
     private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
 
     Network mNetwork;
@@ -123,7 +128,8 @@
         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
                 MyActivity.class, null);
         mPackageName = mActivity.getPackageName();
-        mCM = (ConnectivityManager) mActivity.getSystemService(mActivity.CONNECTIVITY_SERVICE);
+        mCM = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mWifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
         mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity);
         mRemoteSocketFactoryClient.bind();
         mDevice.waitForIdle();
@@ -192,9 +198,11 @@
         }
     }
 
+    // TODO: Consider replacing arguments with a Builder.
     private void startVpn(
         String[] addresses, String[] routes, String allowedApplications,
-        String disallowedApplications, ProxyInfo proxyInfo) throws Exception {
+        String disallowedApplications, @Nullable ProxyInfo proxyInfo,
+        @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered) throws Exception {
         prepareVpn();
 
         // Register a callback so we will be notified when our VPN comes up.
@@ -221,7 +229,10 @@
                 .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
                 .putExtra(mPackageName + ".allowedapplications", allowedApplications)
                 .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
-                .putExtra(mPackageName + ".httpProxy", proxyInfo);
+                .putExtra(mPackageName + ".httpProxy", proxyInfo)
+                .putParcelableArrayListExtra(
+                    mPackageName + ".underlyingNetworks", underlyingNetworks)
+                .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered);
 
         mActivity.startService(intent);
         synchronized (mLock) {
@@ -251,7 +262,8 @@
         mCallback = new NetworkCallback() {
             public void onLost(Network network) {
                 synchronized (mLockShutdown) {
-                    Log.i(TAG, "Got lost callback for network=" + network + ",mNetwork = " + mNetwork);
+                    Log.i(TAG, "Got lost callback for network=" + network
+                            + ",mNetwork = " + mNetwork);
                     if( mNetwork == network){
                         mLockShutdown.notify();
                     }
@@ -574,7 +586,7 @@
 
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                  new String[] {"0.0.0.0/0", "::/0"},
-                 "", "", null);
+                 "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1));
         assertNotNull("Failed to receive broadcast from VPN service", intent);
@@ -598,7 +610,7 @@
         String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
-                 allowedApps, "", null);
+                 allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         assertSocketClosed(fd, TEST_HOST);
 
@@ -620,7 +632,8 @@
         Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps);
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
-                 "", disallowedApps, null);
+                 "", disallowedApps, null, null /* underlyingNetworks */,
+                 false /* isAlwaysMetered */);
 
         assertSocketStillOpen(localFd, TEST_HOST);
         assertSocketStillOpen(remoteFd, TEST_HOST);
@@ -657,7 +670,7 @@
         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
-                testProxyInfo);
+                testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         // Check that the proxy change broadcast is received
         try {
@@ -697,7 +710,7 @@
         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                 new String[] {"0.0.0.0/0", "::/0"}, "", disallowedApps,
-                testProxyInfo);
+                testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         // The disallowed app does has the proxy configs of the default network.
         assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
@@ -711,7 +724,8 @@
         proxyBroadcastReceiver.register();
         String allowedApps = mPackageName;
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
-                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null);
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         try {
             assertNotNull("No proxy change was broadcast.",
@@ -748,7 +762,7 @@
         proxyBroadcastReceiver.register();
         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
-                testProxyInfo);
+                testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
         assertDefaultProxy(testProxyInfo);
         mCM.bindProcessToNetwork(initialNetwork);
@@ -761,6 +775,145 @@
         assertDefaultProxy(initialProxy);
     }
 
+    public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
+        if (!supportedHardware()) {
+            return;
+        }
+        // VPN is not routing any traffic i.e. its underlying networks is an empty array.
+        ArrayList<Network> underlyingNetworks = new ArrayList<>();
+        String allowedApps = mPackageName;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                underlyingNetworks, false /* isAlwaysMetered */);
+
+        // VPN should now be the active network.
+        assertEquals(mNetwork, mCM.getActiveNetwork());
+        assertVpnTransportContains(NetworkCapabilities.TRANSPORT_VPN);
+        // VPN with no underlying networks should be metered by default.
+        assertTrue(isNetworkMetered(mNetwork));
+        assertTrue(mCM.isActiveNetworkMetered());
+    }
+
+    public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
+        if (!supportedHardware()) {
+            return;
+        }
+        Network underlyingNetwork = mCM.getActiveNetwork();
+        if (underlyingNetwork == null) {
+            Log.i(TAG, "testVpnMeterednessWithNullUnderlyingNetwork cannot execute"
+                    + " unless there is an active network");
+            return;
+        }
+        // VPN tracks platform default.
+        ArrayList<Network> underlyingNetworks = null;
+        String allowedApps = mPackageName;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                underlyingNetworks, false /*isAlwaysMetered */);
+
+        // Ensure VPN transports contains underlying network's transports.
+        assertVpnTransportContains(underlyingNetwork);
+        // Its meteredness should be same as that of underlying network.
+        assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
+        // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
+        assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
+    }
+
+    public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
+        if (!supportedHardware()) {
+            return;
+        }
+        Network underlyingNetwork = mCM.getActiveNetwork();
+        if (underlyingNetwork == null) {
+            Log.i(TAG, "testVpnMeterednessWithNonNullUnderlyingNetwork cannot execute"
+                    + " unless there is an active network");
+            return;
+        }
+        // VPN explicitly declares WiFi to be its underlying network.
+        ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
+        underlyingNetworks.add(underlyingNetwork);
+        String allowedApps = mPackageName;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                underlyingNetworks, false /* isAlwaysMetered */);
+
+        // Ensure VPN transports contains underlying network's transports.
+        assertVpnTransportContains(underlyingNetwork);
+        // Its meteredness should be same as that of underlying network.
+        assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
+        // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
+        assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
+    }
+
+    public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
+        if (!supportedHardware()) {
+            return;
+        }
+        Network underlyingNetwork = mCM.getActiveNetwork();
+        if (underlyingNetwork == null) {
+            Log.i(TAG, "testAlwaysMeteredVpnWithNullUnderlyingNetwork cannot execute"
+                    + " unless there is an active network");
+            return;
+        }
+        // VPN tracks platform default.
+        ArrayList<Network> underlyingNetworks = null;
+        String allowedApps = mPackageName;
+        boolean isAlwaysMetered = true;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                underlyingNetworks, isAlwaysMetered);
+
+        // VPN's meteredness does not depend on underlying network since it is always metered.
+        assertTrue(isNetworkMetered(mNetwork));
+        assertTrue(mCM.isActiveNetworkMetered());
+    }
+
+    public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
+        if (!supportedHardware()) {
+            return;
+        }
+        Network underlyingNetwork = mCM.getActiveNetwork();
+        if (underlyingNetwork == null) {
+            Log.i(TAG, "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork cannot execute"
+                    + " unless there is an active network");
+            return;
+        }
+        // VPN explicitly declares its underlying network.
+        ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
+        underlyingNetworks.add(underlyingNetwork);
+        String allowedApps = mPackageName;
+        boolean isAlwaysMetered = true;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
+                underlyingNetworks, isAlwaysMetered);
+
+        // VPN's meteredness does not depend on underlying network since it is always metered.
+        assertTrue(isNetworkMetered(mNetwork));
+        assertTrue(mCM.isActiveNetworkMetered());
+    }
+
+    private boolean isNetworkMetered(Network network) {
+        NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+        return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+    }
+
+    private void assertVpnTransportContains(Network underlyingNetwork) {
+        int[] transports = mCM.getNetworkCapabilities(underlyingNetwork).getTransportTypes();
+        assertVpnTransportContains(transports);
+    }
+
+    private void assertVpnTransportContains(int... transports) {
+        NetworkCapabilities vpnCaps = mCM.getNetworkCapabilities(mNetwork);
+        for (int transport : transports) {
+            assertTrue(vpnCaps.hasTransport(transport));
+        }
+    }
+
     private void assertDefaultProxy(ProxyInfo expected) {
         assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
         String expectedHost = expected == null ? null : expected.getHost();
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 5f5ea43..4598c39 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -81,6 +81,11 @@
                 "testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
     }
 
+    public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+                "testBroadcastNotSentOnUnsupportedDevices");
+    }
+
     /*****************************
      * Battery Saver Mode tests. *
      *****************************/
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index e34ee89..6e37a24 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -64,4 +64,31 @@
     public void testBindToNetworkWithProxy() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy");
     }
+
+    public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNoUnderlyingNetwork");
+    }
+
+    public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNullUnderlyingNetwork");
+    }
+
+    public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNonNullUnderlyingNetwork");
+    }
+
+    public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
+    }
+
+    public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
+        runDeviceTests(
+                TEST_PKG,
+                TEST_PKG + ".VpnTest",
+                "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork");
+    }
 }
diff --git a/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java b/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
index ca14c27..bc2ee2c 100644
--- a/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
+++ b/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
@@ -48,7 +48,7 @@
     }
 
     private void setAppIdleConstants(String appIdleConstants) throws DeviceNotAvailableException {
-        executeCmd("settings put global app_idle_constants " + appIdleConstants);
+        executeCmd("settings put global app_idle_constants \"" + appIdleConstants + "\"");
     }
 
     private String getAppIdleConstants() throws DeviceNotAvailableException {
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index dbd8fef..44a00ef 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -16,7 +16,8 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.net.cts">
+    package="android.net.cts"
+    android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -36,12 +37,6 @@
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
-
-        <receiver android:name=".ConnectivityReceiver">
-            <intent-filter>
-                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
-            </intent-filter>
-        </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/cts/net/AndroidTest.xml b/tests/cts/net/AndroidTest.xml
index ff93afc..1807a6b 100644
--- a/tests/cts/net/AndroidTest.xml
+++ b/tests/cts/net/AndroidTest.xml
@@ -16,11 +16,12 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsNetTestCases.apk" />
-        <option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.net.cts" />
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
new file mode 100644
index 0000000..48161cf
--- /dev/null
+++ b/tests/cts/net/api23Test/Android.bp
@@ -0,0 +1,51 @@
+// 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.
+
+android_test {
+    name: "CtsNetApi23TestCases",
+    defaults: ["cts_defaults"],
+
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+
+    libs: [
+        "android.test.base.stubs",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "core-tests-support",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "mockwebserver",
+        "junit",
+        "junit-params",
+        "truth-prebuilt",
+    ],
+
+    platform_apis: true,
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+}
diff --git a/tests/cts/net/api23Test/AndroidManifest.xml b/tests/cts/net/api23Test/AndroidManifest.xml
new file mode 100644
index 0000000..8af87f6
--- /dev/null
+++ b/tests/cts/net/api23Test/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.net.cts.api23test">
+
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:usesCleartextTraffic="true">
+        <uses-library android:name="android.test.runner" />
+
+        <receiver android:name=".ConnectivityReceiver">
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.net.cts.api23test"
+                     android:label="CTS tests of android.net">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/cts/net/api23Test/AndroidTest.xml b/tests/cts/net/api23Test/AndroidTest.xml
new file mode 100644
index 0000000..21f28fc
--- /dev/null
+++ b/tests/cts/net/api23Test/AndroidTest.xml
@@ -0,0 +1,30 @@
+<!-- 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.
+-->
+<configuration description="Config for CTS Net API23 test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsNetApi23TestCases.apk" />
+        <option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.net.cts.api23test" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
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
new file mode 100644
index 0000000..f38490e
--- /dev/null
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -0,0 +1,399 @@
+/*
+ * 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.api23test;
+
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+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.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;
+
+    @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();
+    }
+
+    /**
+     * Tests reporting of connectivity changed.
+     */
+    public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
+            return;
+        }
+        ConnectivityReceiver.prepare();
+
+        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.
+        Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
+        finalIntent.setClass(mContext, ConnectivityReceiver.class);
+        mContext.sendBroadcast(finalIntent);
+        assertFalse(ConnectivityReceiver.waitForBroadcast());
+    }
+
+    public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
+            throws InterruptedException {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot"
+                    + "execute unless device supports WiFi");
+            return;
+        }
+        mContext.startActivity(new Intent()
+                .setComponent(new ComponentName("android.net.cts.appForApi23",
+                        "android.net.cts.appForApi23.ConnectivityListeningActivity"))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        Thread.sleep(200);
+
+        toggleWifi();
+
+        Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
+        assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
+                getConnectivityCount, SEND_BROADCAST_TIMEOUT));
+    }
+
+    public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
+        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+            Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
+            return;
+        }
+        ConnectivityReceiver.prepare();
+        ConnectivityReceiver receiver = new ConnectivityReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(receiver, filter);
+
+        toggleWifi();
+        Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
+        finalIntent.setClass(mContext, ConnectivityReceiver.class);
+        mContext.sendBroadcast(finalIntent);
+
+        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);
+        mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                result.offer(getResultCode());
+            }
+        }, null, 0, null, null);
+
+        Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        assertNotNull("Timed out (more than " + timeoutMs +
+                " milliseconds) waiting for result code for broadcast", resultCode);
+        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/ConnectivityReceiver.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
similarity index 98%
rename from tests/cts/net/src/android/net/cts/ConnectivityReceiver.java
rename to tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
index 6a7b4a0..9d2b8ad 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityReceiver.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.cts;
+package android.net.cts.api23test;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
diff --git a/tests/cts/net/src/android/net/cts/AirplaneModeTest.java b/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
index 0a3146c..524e549 100644
--- a/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
+++ b/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
@@ -18,13 +18,14 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import java.lang.Thread;
 
+@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
 public class AirplaneModeTest extends AndroidTestCase {
     private static final String TAG = "AirplaneModeTest";
     private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index cb1aa09..c444445 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -35,7 +35,6 @@
 import android.app.PendingIntent;
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -43,6 +42,8 @@
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -52,11 +53,13 @@
 import android.net.NetworkInfo.State;
 import android.net.NetworkRequest;
 import android.net.SocketKeepalive;
+import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
 import android.os.Looper;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
 import android.system.Os;
 import android.system.OsConstants;
@@ -86,6 +89,7 @@
 import java.net.URL;
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -99,8 +103,6 @@
 
     private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
 
-    private static final String FEATURE_ENABLE_HIPRI = "enableHIPRI";
-
     public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
     public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
 
@@ -110,7 +112,6 @@
     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;
-    private static final int SEND_BROADCAST_TIMEOUT = 30000;
     private static final int MIN_KEEPALIVE_INTERVAL = 10;
     private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
@@ -125,10 +126,6 @@
     private static final String NETWORK_CALLBACK_ACTION =
             "ConnectivityManagerTest.NetworkCallbackAction";
 
-    // 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";
-
     // device could have only one interface: data, wifi.
     private static final int MIN_NUM_NETWORK_TYPES = 1;
 
@@ -294,6 +291,7 @@
      * Tests that connections can be opened on WiFi and cellphone networks,
      * and that they are made from different IP addresses.
      */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testOpenConnection() throws Exception {
         boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
                 && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
@@ -456,6 +454,7 @@
      * WiFi. We could add a version that uses the telephony data connection but it's not clear
      * that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
      */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testRegisterNetworkCallback() {
         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -496,6 +495,7 @@
      * {@link #testRegisterNetworkCallback} except that a {@code PendingIntent} is used instead
      * of a {@code NetworkCallback}.
      */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testRegisterNetworkCallback_withPendingIntent() {
         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -540,6 +540,7 @@
      * Exercises the requestNetwork with NetworkCallback API. This checks to
      * see if we get a callback for an INTERNET request.
      */
+    @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testRequestNetworkCallback() {
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.requestNetwork(new NetworkRequest.Builder()
@@ -562,6 +563,7 @@
      * Exercises the requestNetwork with NetworkCallback API with timeout - expected to
      * fail. Use WIFI and switch Wi-Fi off.
      */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testRequestNetworkCallback_onUnavailable() {
         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
         if (previousWifiEnabledState) {
@@ -587,93 +589,6 @@
         }
     }
 
-    /**
-     * Tests reporting of connectivity changed.
-     */
-    public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
-        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
-            Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
-            return;
-        }
-        ConnectivityReceiver.prepare();
-
-        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.
-        Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
-        finalIntent.setClass(mContext, ConnectivityReceiver.class);
-        mContext.sendBroadcast(finalIntent);
-        assertFalse(ConnectivityReceiver.waitForBroadcast());
-    }
-
-    public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
-        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
-            Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
-            return;
-        }
-        ConnectivityReceiver.prepare();
-        ConnectivityReceiver receiver = new ConnectivityReceiver();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        mContext.registerReceiver(receiver, filter);
-
-        toggleWifi();
-        Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
-        finalIntent.setClass(mContext, ConnectivityReceiver.class);
-        mContext.sendBroadcast(finalIntent);
-
-        assertTrue(ConnectivityReceiver.waitForBroadcast());
-    }
-
-    public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
-            throws InterruptedException {
-        if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
-            Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
-            return;
-        }
-        mContext.startActivity(new Intent()
-                .setComponent(new ComponentName("android.net.cts.appForApi23",
-                        "android.net.cts.appForApi23.ConnectivityListeningActivity"))
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        Thread.sleep(200);
-
-        toggleWifi();
-
-        Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
-        assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
-                getConnectivityCount, SEND_BROADCAST_TIMEOUT));
-    }
-
-    private int sendOrderedBroadcastAndReturnResultCode(
-            Intent intent, int timeoutMs) throws InterruptedException {
-        final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
-        mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                result.offer(getResultCode());
-            }
-        }, null, 0, null, null);
-
-        Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
-        assertNotNull("Timed out (more than " + timeoutMs +
-                " milliseconds) waiting for result code for broadcast", resultCode);
-        return resultCode;
-    }
-
-    // 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);
-        }
-    }
-
     /** Enable WiFi and wait for it to become connected to a network. */
     private Network connectToWifi() {
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -705,6 +620,16 @@
         return wifiNetwork;
     }
 
+    private InetAddress getFirstV4Address(Network network) {
+        LinkProperties linkProperties = mCm.getLinkProperties(network);
+        for (InetAddress address : linkProperties.getAddresses()) {
+            if (address instanceof Inet4Address) {
+                return address;
+            }
+        }
+        return null;
+    }
+
     private Socket getBoundSocket(Network network, String host, int port) throws IOException {
         InetSocketAddress addr = new InetSocketAddress(host, port);
         Socket s = network.getSocketFactory().createSocket();
@@ -899,6 +824,7 @@
     }
 
     /** Verify restricted networks cannot be requested. */
+    @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testRestrictedNetworks() {
         // Verify we can request unrestricted networks:
         NetworkRequest request = new NetworkRequest.Builder()
@@ -1016,6 +942,7 @@
      * Verify that getMultipathPreference does return appropriate values
      * for metered and unmetered networks.
      */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testGetMultipathPreference() throws Exception {
         final ContentResolver resolver = mContext.getContentResolver();
         final Network network = ensureWifiConnected();
@@ -1171,6 +1098,16 @@
         return s;
     }
 
+    private int getSupportedKeepalivesFromRes() throws Exception {
+        final Network network = ensureWifiConnected();
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+
+        // Get number of supported concurrent keepalives for testing network.
+        final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(mContext);
+        return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+                keepalivesPerTransport, nc);
+    }
+
     private boolean isKeepaliveSupported() throws Exception {
         final Network network = ensureWifiConnected();
         final Executor executor = mContext.getMainExecutor();
@@ -1207,6 +1144,7 @@
         }
     }
 
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testCreateTcpKeepalive() throws Exception {
         adoptShellPermissionIdentity();
 
@@ -1278,4 +1216,150 @@
 
         }
     }
+
+    private int createConcurrentSocketKeepalives(int nattCount, int tcpCount) throws Exception {
+        // Use customization value in resource to prevent the need of privilege.
+        if (getSupportedKeepalivesFromRes() == 0) return 0;
+
+        final Network network = ensureWifiConnected();
+
+        final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
+        final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+        final Executor executor = mContext.getMainExecutor();
+
+        // Create concurrent TCP keepalives.
+        for (int i = 0; i < tcpCount; i++) {
+            // Assert that TCP connections can be established on wifi. The file descriptor of tcp
+            // sockets will be duplicated and kept valid in service side if the keepalives are
+            // successfully started.
+            try (Socket tcpSocket = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+                        0 /* Unused */, AF_INET)) {
+                final SocketKeepalive ka = mCm.createSocketKeepalive(network, tcpSocket, executor,
+                        callback);
+                ka.start(MIN_KEEPALIVE_INTERVAL);
+                TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
+                assertNotNull(cv);
+                if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
+                        && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+                    // Limit reached.
+                    break;
+                }
+                if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
+                    kalist.add(ka);
+                } else {
+                    fail("Unexpected error when creating " + (i + 1) + " TCP keepalives: " + cv);
+                }
+            }
+        }
+
+        // Assert that a Nat-T socket can be created.
+        final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+        final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
+
+        final InetAddress srcAddr = getFirstV4Address(network);
+        final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
+        assertNotNull(srcAddr);
+        assertNotNull(dstAddr);
+
+        // Test concurrent Nat-T keepalives.
+        for (int i = 0; i < nattCount; i++) {
+            final SocketKeepalive ka = mCm.createSocketKeepalive(network, nattSocket,
+                    srcAddr, dstAddr, executor, callback);
+            ka.start(MIN_KEEPALIVE_INTERVAL);
+            TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
+            assertNotNull(cv);
+            if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
+                    && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+                // Limit reached.
+                break;
+            }
+            if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
+                kalist.add(ka);
+            } else {
+                fail("Unexpected error when creating " + (i + 1) + " Nat-T keepalives: " + cv);
+            }
+        }
+
+        final int ret = kalist.size();
+
+        // Clean up.
+        for (final SocketKeepalive ka : kalist) {
+            ka.stop();
+            callback.expectStopped();
+        }
+        kalist.clear();
+        nattSocket.close();
+
+        return ret;
+    }
+
+    /**
+     * Verifies that the concurrent keepalive slots meet the minimum requirement, and don't
+     * get leaked after iterations.
+     */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    public void testSocketKeepaliveLimit() throws Exception {
+        adoptShellPermissionIdentity();
+
+        final int supported = getSupportedKeepalivesFromRes();
+
+        if (!isKeepaliveSupported()) {
+            // Sanity check.
+            assertEquals(0, supported);
+            return;
+        }
+
+        // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
+        assertGreaterOrEqual(supported, KeepaliveUtils.MIN_SUPPORTED_KEEPALIVE_COUNT);
+
+        // Verifies that different types of keepalives can be established.
+        assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
+        assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+
+        // Verifies that different types can be established at the same time.
+        assertEquals(supported, createConcurrentSocketKeepalives(
+                supported / 2, supported - supported / 2));
+
+        // Verifies that keepalives don't get leaked in second round.
+        assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
+        assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+        assertEquals(supported, createConcurrentSocketKeepalives(
+                supported / 2, supported - supported / 2));
+
+        dropShellPermissionIdentity();
+    }
+
+    /**
+     * Verifies that the keepalive slots are limited as customized for unprivileged requests.
+     */
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    public void testSocketKeepaliveUnprivileged() throws Exception {
+        final int supported = getSupportedKeepalivesFromRes();
+
+        adoptShellPermissionIdentity();
+        if (!isKeepaliveSupported()) {
+            // Sanity check.
+            assertEquals(0, supported);
+            return;
+        }
+        dropShellPermissionIdentity();
+
+        final int allowedUnprivilegedPerUid = mContext.getResources().getInteger(
+                R.integer.config_allowedUnprivilegedKeepalivePerUid);
+        final int reservedPrivilegedSlots = mContext.getResources().getInteger(
+                R.integer.config_reservedPrivilegedKeepaliveSlots);
+        // Verifies that unprivileged request per uid cannot exceed the limit customized in the
+        // resource. Currently, unprivileged keepalive slots are limited to Nat-T only, this test
+        // does not apply to TCP.
+        assertGreaterOrEqual(supported, reservedPrivilegedSlots);
+        assertGreaterOrEqual(supported, allowedUnprivilegedPerUid);
+        final int expectedUnprivileged =
+                Math.min(allowedUnprivilegedPerUid, supported - reservedPrivilegedSlots);
+        assertEquals(expectedUnprivileged, createConcurrentSocketKeepalives(supported + 1, 0));
+    }
+
+    private static void assertGreaterOrEqual(long greater, long lesser) {
+        assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
+                greater >= lesser);
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 35d0f48..26049cc 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -23,16 +23,18 @@
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
+import android.platform.test.annotations.AppModeFull;
 import android.system.Os;
 import android.system.OsConstants;
-import android.test.AndroidTestCase;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
-import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -42,7 +44,12 @@
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class IpSecBaseTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class IpSecBaseTest {
 
     private static final String TAG = IpSecBaseTest.class.getSimpleName();
 
@@ -69,11 +76,19 @@
 
     protected ConnectivityManager mCM;
     protected IpSecManager mISM;
+    protected Context mContext;
 
-    protected void setUp() throws Exception {
-        super.setUp();
-        mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
-        mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mISM =
+                (IpSecManager)
+                        InstrumentationRegistry.getContext()
+                                .getSystemService(Context.IPSEC_SERVICE);
+        mCM =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
     protected static byte[] getKey(int bitLength) {
@@ -195,6 +210,17 @@
     public static class JavaUdpSocket implements GenericUdpSocket {
         public final DatagramSocket mSocket;
 
+        public JavaUdpSocket(InetAddress localAddr, int port) {
+            try {
+                mSocket = new DatagramSocket(port, localAddr);
+                mSocket.setSoTimeout(SOCK_TIMEOUT);
+            } catch (SocketException e) {
+                // Fail loudly if we can't set up sockets properly. And without the timeout, we
+                // could easily end up in an endless wait.
+                throw new RuntimeException(e);
+            }
+        }
+
         public JavaUdpSocket(InetAddress localAddr) {
             try {
                 mSocket = new DatagramSocket(0, localAddr);
@@ -425,26 +451,25 @@
     }
 
     protected static IpSecTransform buildIpSecTransform(
-            Context mContext,
+            Context context,
             IpSecManager.SecurityParameterIndex spi,
             IpSecManager.UdpEncapsulationSocket encapSocket,
             InetAddress remoteAddr)
             throws Exception {
-        String localAddr = (remoteAddr instanceof Inet4Address) ? IPV4_LOOPBACK : IPV6_LOOPBACK;
         IpSecTransform.Builder builder =
-                new IpSecTransform.Builder(mContext)
-                .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
-                .setAuthentication(
-                        new IpSecAlgorithm(
-                                IpSecAlgorithm.AUTH_HMAC_SHA256,
-                                AUTH_KEY,
-                                AUTH_KEY.length * 4));
+                new IpSecTransform.Builder(context)
+                        .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
+                        .setAuthentication(
+                                new IpSecAlgorithm(
+                                        IpSecAlgorithm.AUTH_HMAC_SHA256,
+                                        AUTH_KEY,
+                                        AUTH_KEY.length * 4));
 
         if (encapSocket != null) {
             builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
         }
 
-        return builder.buildTransportModeTransform(InetAddress.getByName(localAddr), spi);
+        return builder.buildTransportModeTransform(remoteAddr, spi);
     }
 
     private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {
@@ -454,6 +479,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testJavaTcpSocketPair() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
@@ -464,6 +491,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testJavaUdpSocketPair() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
@@ -475,6 +504,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testJavaUdpSocketPairUnconnected() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
@@ -486,6 +517,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testNativeTcpSocketPair() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
@@ -497,6 +530,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testNativeUdpSocketPair() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
@@ -508,6 +543,8 @@
         }
     }
 
+    @Test
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testNativeUdpSocketPairUnconnected() throws Exception {
         for (String addr : LOOPBACK_ADDRS) {
             InetAddress local = InetAddress.getByName(addr);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 3387064..1241785 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -16,19 +16,33 @@
 
 package android.net.cts;
 
+import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
+import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
+
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
 import android.net.TrafficStats;
+import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -37,6 +51,12 @@
 import java.net.InetAddress;
 import java.util.Arrays;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
 public class IpSecManagerTest extends IpSecBaseTest {
 
     private static final String TAG = IpSecManagerTest.class.getSimpleName();
@@ -53,18 +73,9 @@
 
     private static final byte[] AEAD_KEY = getKey(288);
 
-    private static final int TCP_HDRLEN_WITH_OPTIONS = 32;
-    private static final int UDP_HDRLEN = 8;
-    private static final int IP4_HDRLEN = 20;
-    private static final int IP6_HDRLEN = 40;
-
-    // Encryption parameters
-    private static final int AES_GCM_IV_LEN = 8;
-    private static final int AES_CBC_IV_LEN = 16;
-    private static final int AES_GCM_BLK_SIZE = 4;
-    private static final int AES_CBC_BLK_SIZE = 16;
-
-    protected void setUp() throws Exception {
+    @Before
+    @Override
+    public void setUp() throws Exception {
         super.setUp();
     }
 
@@ -74,6 +85,7 @@
      * Realloc the same SPI that was specifically created (expect SpiUnavailable)
      * Close SPIs
      */
+    @Test
     public void testAllocSpi() throws Exception {
         for (InetAddress addr : GOOGLE_DNS_LIST) {
             IpSecManager.SecurityParameterIndex randomSpi = null, droidSpi = null;
@@ -225,6 +237,7 @@
      * release transform
      * send data (expect exception)
      */
+    @Test
     public void testCreateTransform() throws Exception {
         InetAddress localAddr = InetAddress.getByName(IPV4_LOOPBACK);
         IpSecManager.SecurityParameterIndex spi =
@@ -432,19 +445,6 @@
         }
     }
 
-    /** Helper function to calculate expected ESP packet size. */
-    private int calculateEspPacketSize(
-            int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
-        final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
-        final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
-        payloadLen += cryptIvLength; // Initialization Vector
-        payloadLen += 2; // ESP trailer
-
-        // Align to block size of encryption algorithm
-        payloadLen += (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize;
-        return payloadLen + ESP_HDRLEN + ICV_LEN;
-    }
-
     public void checkTransform(
             int protocol,
             String localAddress,
@@ -485,7 +485,7 @@
             try (IpSecTransform transform =
                         transformBuilder.buildTransportModeTransform(local, spi)) {
                 if (protocol == IPPROTO_TCP) {
-                    transportHdrLen = TCP_HDRLEN_WITH_OPTIONS;
+                    transportHdrLen = TCP_HDRLEN_WITH_TIMESTAMP_OPT;
                     checkTcp(transform, local, sendCount, useJavaSockets);
                 } else if (protocol == IPPROTO_UDP) {
                     transportHdrLen = UDP_HDRLEN;
@@ -522,7 +522,7 @@
 
         int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen;
         int outerPacketSize =
-                calculateEspPacketSize(
+                PacketUtils.calculateEspPacketSize(
                                 TEST_DATA.length + transportHdrLen, ivLen, blkSize, truncLenBits)
                         + udpEncapLen
                         + ipHdrLen;
@@ -540,13 +540,13 @@
         // Add TCP ACKs for data packets
         if (protocol == IPPROTO_TCP) {
             int encryptedTcpPktSize =
-                    calculateEspPacketSize(TCP_HDRLEN_WITH_OPTIONS, ivLen, blkSize, truncLenBits);
+                    PacketUtils.calculateEspPacketSize(
+                            TCP_HDRLEN_WITH_TIMESTAMP_OPT, ivLen, blkSize, truncLenBits);
 
-
-                // Add data packet ACKs
-                expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount);
-                expectedInnerBytes += (TCP_HDRLEN_WITH_OPTIONS + ipHdrLen) * (sendCount);
-                expectedPackets += sendCount;
+            // Add data packet ACKs
+            expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount);
+            expectedInnerBytes += (TCP_HDRLEN_WITH_TIMESTAMP_OPT + ipHdrLen) * (sendCount);
+            expectedPackets += sendCount;
         }
 
         StatsChecker.waitForNumPackets(expectedPackets);
@@ -607,6 +607,7 @@
         }
     }
 
+    @Test
     public void testIkeOverUdpEncapSocket() throws Exception {
         // IPv6 not supported for UDP-encap-ESP
         InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
@@ -665,24 +666,28 @@
     //     checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
     // }
 
+    @Test
     public void testInterfaceCountersUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1000, false);
     }
 
+    @Test
     public void testInterfaceCountersUdp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1000, false);
     }
 
+    @Test
     public void testInterfaceCountersUdp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1000, false);
     }
 
+    @Test
     public void testAesCbcHmacMd5Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -690,6 +695,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacMd5Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -697,6 +703,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacMd5Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -704,6 +711,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacMd5Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -711,6 +719,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -718,6 +727,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -725,6 +735,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -732,6 +743,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -739,6 +751,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -746,6 +759,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -753,6 +767,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -760,6 +775,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -767,6 +783,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -774,6 +791,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -781,6 +799,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -788,6 +807,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -795,6 +815,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Tcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -802,6 +823,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Tcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -809,6 +831,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Udp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -816,6 +839,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Udp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -823,6 +847,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
     }
 
+    @Test
     public void testAesGcm64Tcp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -830,6 +855,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm64Tcp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -837,6 +863,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm64Udp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -844,6 +871,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm64Udp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -851,6 +879,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm96Tcp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -858,6 +887,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm96Tcp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -865,6 +895,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm96Udp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -872,6 +903,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm96Udp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -879,6 +911,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm128Tcp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -886,6 +919,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm128Tcp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -893,6 +927,7 @@
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm128Udp4() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -900,6 +935,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesGcm128Udp6() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -907,6 +943,7 @@
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -914,6 +951,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacMd5Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -921,6 +959,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -928,6 +967,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha1Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -935,6 +975,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -942,6 +983,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha256Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -949,6 +991,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -956,6 +999,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha384Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -963,6 +1007,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -970,6 +1015,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesCbcHmacSha512Udp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -977,6 +1023,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
     }
 
+    @Test
     public void testAesGcm64Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -984,6 +1031,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testAesGcm64Udp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -991,6 +1039,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testAesGcm96Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -998,6 +1047,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testAesGcm96Udp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -1005,6 +1055,7 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testAesGcm128Tcp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -1012,6 +1063,7 @@
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testAesGcm128Udp4UdpEncap() throws Exception {
         IpSecAlgorithm authCrypt =
                 new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -1019,78 +1071,91 @@
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
     }
 
+    @Test
     public void testCryptUdp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
+    @Test
     public void testAuthUdp4() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
     }
 
+    @Test
     public void testCryptUdp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
+    @Test
     public void testAuthUdp6() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
         checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
     }
 
+    @Test
     public void testCryptTcp4() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
+    @Test
     public void testAuthTcp4() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
     }
 
+    @Test
     public void testCryptTcp6() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
     }
 
+    @Test
     public void testAuthTcp6() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
         checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
     }
 
+    @Test
     public void testCryptUdp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
     }
 
+    @Test
     public void testAuthUdp4UdpEncap() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
         checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
     }
 
+    @Test
     public void testCryptTcp4UdpEncap() throws Exception {
         IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
     }
 
+    @Test
     public void testAuthTcp4UdpEncap() throws Exception {
         IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
         checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
     }
 
+    @Test
     public void testOpenUdpEncapSocketSpecificPort() throws Exception {
         IpSecManager.UdpEncapsulationSocket encapSocket = null;
         int port = -1;
@@ -1119,6 +1184,7 @@
         assertTrue("Returned invalid port", encapSocket.getPort() == port);
     }
 
+    @Test
     public void testOpenUdpEncapSocketRandomPort() throws Exception {
         try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
             assertTrue("Returned invalid port", encapSocket.getPort() != 0);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 5dc9b63..93638ac 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -16,161 +16,729 @@
 
 package android.net.cts;
 
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.IpSecManager.UdpEncapsulationSocket;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
+import static android.net.cts.PacketUtils.BytePayload;
+import static android.net.cts.PacketUtils.EspHeader;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.Ip4Header;
+import static android.net.cts.PacketUtils.Ip6Header;
+import static android.net.cts.PacketUtils.IpHeader;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.net.cts.PacketUtils.UdpHeader;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
 import android.net.IpSecTransform;
+import android.net.LinkAddress;
 import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.cts.PacketUtils.Payload;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 public class IpSecManagerTunnelTest extends IpSecBaseTest {
-
     private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
-    private static final int IP4_PREFIX_LEN = 24;
-    private static final int IP6_PREFIX_LEN = 48;
-    private static final InetAddress OUTER_ADDR4 = InetAddress.parseNumericAddress("192.0.2.0");
-    private static final InetAddress OUTER_ADDR6 =
-            InetAddress.parseNumericAddress("2001:db8:f00d::1");
-    private static final InetAddress INNER_ADDR4 = InetAddress.parseNumericAddress("10.0.0.1");
-    private static final InetAddress INNER_ADDR6 =
-            InetAddress.parseNumericAddress("2001:db8:d00d::1");
 
-    private Network mUnderlyingNetwork;
-    private Network mIpSecNetwork;
+    private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
+    private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2");
+    private static final InetAddress LOCAL_OUTER_6 =
+            InetAddress.parseNumericAddress("2001:db8:1::1");
+    private static final InetAddress REMOTE_OUTER_6 =
+            InetAddress.parseNumericAddress("2001:db8:1::2");
 
-    protected void setUp() throws Exception {
+    private static final InetAddress LOCAL_INNER_4 =
+            InetAddress.parseNumericAddress("198.51.100.1");
+    private static final InetAddress REMOTE_INNER_4 =
+            InetAddress.parseNumericAddress("198.51.100.2");
+    private static final InetAddress LOCAL_INNER_6 =
+            InetAddress.parseNumericAddress("2001:db8:2::1");
+    private static final InetAddress REMOTE_INNER_6 =
+            InetAddress.parseNumericAddress("2001:db8:2::2");
+
+    private static final int IP4_PREFIX_LEN = 32;
+    private static final int IP6_PREFIX_LEN = 128;
+
+    private static final int TIMEOUT_MS = 500;
+
+    // Static state to reduce setup/teardown
+    private static ConnectivityManager sCM;
+    private static TestNetworkManager sTNM;
+    private static ParcelFileDescriptor sTunFd;
+    private static TestNetworkCallback sTunNetworkCallback;
+    private static Network sTunNetwork;
+    private static TunUtils sTunUtils;
+
+    private static Context sContext = InstrumentationRegistry.getContext();
+    private static IBinder sBinder = new Binder();
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+        sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
+
+        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
+        // a standard permission is insufficient. So we shell out the appop, to give us the
+        // right appop permissions.
+        setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+
+        TestNetworkInterface testIntf =
+                sTNM.createTunInterface(
+                        new LinkAddress[] {
+                            new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
+                            new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
+                        });
+
+        sTunFd = testIntf.getFileDescriptor();
+        sTunNetworkCallback = setupAndGetTestNetwork(testIntf.getInterfaceName());
+        sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
+
+        sTunUtils = new TunUtils(sTunFd);
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
         super.setUp();
+
+        // Set to true before every run; some tests flip this.
+        setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+
+        // Clear sTunUtils state
+        sTunUtils.reset();
     }
 
-    protected void tearDown() {
-        setAppop(false);
+    @AfterClass
+    public static void tearDownAfterClass() throws Exception {
+        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+
+        sCM.unregisterNetworkCallback(sTunNetworkCallback);
+
+        sTNM.teardownTestNetwork(sTunNetwork);
+        sTunFd.close();
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
     }
 
-    private void setAppop(boolean allow) {
-        // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted by the
-        // telephony framework, and the only permission that is sufficient is NETWORK_STACK. So we
-        // shell out the appop manager, to give us the right appop permissions.
-        String cmd =
-                "appops set "
-                        + mContext.getPackageName()
-                        + " MANAGE_IPSEC_TUNNELS "
-                + (allow ? "allow" : "deny");
-        SystemUtil.runShellCommand(cmd);
+    private static boolean hasTunnelsFeature() {
+        return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+                || SystemProperties.getInt("ro.product.first_api_level", 0)
+                        >= Build.VERSION_CODES.Q;
     }
 
-    public void testSecurityExceptionsCreateTunnelInterface() throws Exception {
+    private static void setAppop(int appop, boolean allow) {
+        String opName = AppOpsManager.opToName(appop);
+        for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
+            String cmd =
+                    String.format(
+                            "appops set %s %s %s",
+                            pkg, // Package name
+                            opName, // Appop
+                            (allow ? "allow" : "deny")); // Action
+            SystemUtil.runShellCommand(cmd);
+        }
+    }
+
+    private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
+        // Build a network request
+        NetworkRequest nr =
+                new NetworkRequest.Builder()
+                        .addTransportType(TRANSPORT_TEST)
+                        .removeCapability(NET_CAPABILITY_TRUSTED)
+                        .removeCapability(NET_CAPABILITY_NOT_VPN)
+                        .setNetworkSpecifier(ifname)
+                        .build();
+
+        TestNetworkCallback cb = new TestNetworkCallback();
+        sCM.requestNetwork(nr, cb);
+
+        // Setup the test network after network request is filed to prevent Network from being
+        // reaped due to no requests matching it.
+        sTNM.setupTestNetwork(ifname, sBinder);
+
+        return cb;
+    }
+
+    @Test
+    public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
+        if (!hasTunnelsFeature()) return;
+
         // Ensure we don't have the appop. Permission is not requested in the Manifest
-        setAppop(false);
+        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
 
         // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
         try {
-            mISM.createIpSecTunnelInterface(OUTER_ADDR6, OUTER_ADDR6, mUnderlyingNetwork);
+            mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork);
             fail("Did not throw SecurityException for Tunnel creation without appop");
         } catch (SecurityException expected) {
         }
     }
 
-    public void testSecurityExceptionsBuildTunnelTransform() throws Exception {
+    @Test
+    public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
+        if (!hasTunnelsFeature()) return;
+
         // Ensure we don't have the appop. Permission is not requested in the Manifest
-        setAppop(false);
+        setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
 
         // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
         try (IpSecManager.SecurityParameterIndex spi =
-                mISM.allocateSecurityParameterIndex(OUTER_ADDR4);
+                        mISM.allocateSecurityParameterIndex(LOCAL_INNER_4);
                 IpSecTransform transform =
-                        new IpSecTransform.Builder(mContext)
-                                .buildTunnelModeTransform(OUTER_ADDR4, spi)) {
+                        new IpSecTransform.Builder(sContext)
+                                .buildTunnelModeTransform(REMOTE_INNER_4, spi)) {
             fail("Did not throw SecurityException for Transform creation without appop");
         } catch (SecurityException expected) {
         }
     }
 
-    private void checkTunnel(InetAddress inner, InetAddress outer, boolean useEncap)
+    /* Test runnables for callbacks after IPsec tunnels are set up. */
+    private interface TestRunnable {
+        void run(Network ipsecNetwork) throws Exception;
+    }
+
+    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final CompletableFuture<Network> futureNetwork = new CompletableFuture<>();
+
+        @Override
+        public void onAvailable(Network network) {
+            futureNetwork.complete(network);
+        }
+
+        public Network getNetworkBlocking() throws Exception {
+            return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private int getPacketSize(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+        int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
+
+        // Inner Transport mode packet size
+        if (transportInTunnelMode) {
+            expectedPacketSize =
+                    PacketUtils.calculateEspPacketSize(
+                            expectedPacketSize,
+                            AES_CBC_IV_LEN,
+                            AES_CBC_BLK_SIZE,
+                            AUTH_KEY.length * 4);
+        }
+
+        // Inner IP Header
+        expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
+
+        // Tunnel mode transform size
+        expectedPacketSize =
+                PacketUtils.calculateEspPacketSize(
+                        expectedPacketSize, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, AUTH_KEY.length * 4);
+
+        // UDP encap size
+        expectedPacketSize += useEncap ? UDP_HDRLEN : 0;
+
+        // Outer IP Header
+        expectedPacketSize += outerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
+
+        return expectedPacketSize;
+    }
+
+    private interface TestRunnableFactory {
+        TestRunnable getTestRunnable(
+                boolean transportInTunnelMode,
+                int spi,
+                InetAddress localInner,
+                InetAddress remoteInner,
+                InetAddress localOuter,
+                InetAddress remoteOuter,
+                IpSecTransform inTransportTransform,
+                IpSecTransform outTransportTransform,
+                int encapPort,
+                int expectedPacketSize)
+                throws Exception;
+    }
+
+    private class OutputTestRunnableFactory implements TestRunnableFactory {
+        public TestRunnable getTestRunnable(
+                boolean transportInTunnelMode,
+                int spi,
+                InetAddress localInner,
+                InetAddress remoteInner,
+                InetAddress localOuter,
+                InetAddress remoteOuter,
+                IpSecTransform inTransportTransform,
+                IpSecTransform outTransportTransform,
+                int encapPort,
+                int expectedPacketSize) {
+            return new TestRunnable() {
+                @Override
+                public void run(Network ipsecNetwork) throws Exception {
+                    // Build a socket and send traffic
+                    JavaUdpSocket socket = new JavaUdpSocket(localInner);
+                    ipsecNetwork.bindSocket(socket.mSocket);
+
+                    // For Transport-In-Tunnel mode, apply transform to socket
+                    if (transportInTunnelMode) {
+                        mISM.applyTransportModeTransform(
+                                socket.mSocket, IpSecManager.DIRECTION_IN, inTransportTransform);
+                        mISM.applyTransportModeTransform(
+                                socket.mSocket, IpSecManager.DIRECTION_OUT, outTransportTransform);
+                    }
+
+                    socket.sendTo(TEST_DATA, remoteInner, socket.getPort());
+
+                    // Verify that an encrypted packet is sent. As of right now, checking encrypted
+                    // body is not possible, due to our not knowing some of the fields of the
+                    // inner IP header (flow label, flags, etc)
+                    sTunUtils.awaitEspPacketNoPlaintext(
+                            spi, TEST_DATA, encapPort != 0, expectedPacketSize);
+
+                    socket.close();
+                }
+            };
+        }
+    }
+
+    private class InputPacketGeneratorTestRunnableFactory implements TestRunnableFactory {
+        public TestRunnable getTestRunnable(
+                boolean transportInTunnelMode,
+                int spi,
+                InetAddress localInner,
+                InetAddress remoteInner,
+                InetAddress localOuter,
+                InetAddress remoteOuter,
+                IpSecTransform inTransportTransform,
+                IpSecTransform outTransportTransform,
+                int encapPort,
+                int expectedPacketSize)
+                throws Exception {
+            return new TestRunnable() {
+                @Override
+                public void run(Network ipsecNetwork) throws Exception {
+                    // Build a socket and receive traffic
+                    JavaUdpSocket socket = new JavaUdpSocket(localInner);
+                    // JavaUdpSocket socket = new JavaUdpSocket(localInner, socketPort.get());
+                    ipsecNetwork.bindSocket(socket.mSocket);
+
+                    // For Transport-In-Tunnel mode, apply transform to socket
+                    if (transportInTunnelMode) {
+                        mISM.applyTransportModeTransform(
+                                socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
+                        mISM.applyTransportModeTransform(
+                                socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
+                    }
+
+                    byte[] pkt;
+                    if (transportInTunnelMode) {
+                        pkt =
+                                getTransportInTunnelModePacket(
+                                        spi,
+                                        spi,
+                                        remoteInner,
+                                        localInner,
+                                        remoteOuter,
+                                        localOuter,
+                                        socket.getPort(),
+                                        encapPort);
+                    } else {
+                        pkt =
+                                getTunnelModePacket(
+                                        spi,
+                                        remoteInner,
+                                        localInner,
+                                        remoteOuter,
+                                        localOuter,
+                                        socket.getPort(),
+                                        encapPort);
+                    }
+                    sTunUtils.injectPacket(pkt);
+
+                    // Receive packet from socket, and validate
+                    receiveAndValidatePacket(socket);
+
+                    socket.close();
+                }
+            };
+        }
+    }
+
+    private void checkTunnelOutput(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
-        setAppop(true);
-        int innerPrefixLen = inner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new OutputTestRunnableFactory());
+    }
 
-        try (IpSecManager.SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(outer);
+    private void checkTunnelInput(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new InputPacketGeneratorTestRunnableFactory());
+    }
+
+    public void checkTunnel(
+            int innerFamily,
+            int outerFamily,
+            boolean useEncap,
+            boolean transportInTunnelMode,
+            TestRunnableFactory factory)
+            throws Exception {
+        if (!hasTunnelsFeature()) return;
+
+        InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
+        InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
+
+        InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
+        InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
+
+        // Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
+        // Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across tunnel
+        // and transport mode, packets are encrypted/decrypted properly based on the src/dst.
+        int spi = getRandomSpi(localOuter, remoteOuter);
+        int expectedPacketSize =
+                getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+
+        try (IpSecManager.SecurityParameterIndex inTransportSpi =
+                        mISM.allocateSecurityParameterIndex(localInner, spi);
+                IpSecManager.SecurityParameterIndex outTransportSpi =
+                        mISM.allocateSecurityParameterIndex(remoteInner, spi);
+                IpSecTransform inTransportTransform =
+                        buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
+                IpSecTransform outTransportTransform =
+                        buildIpSecTransform(sContext, outTransportSpi, null, localInner);
+                UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+
+            buildTunnelAndNetwork(
+                    localInner,
+                    remoteInner,
+                    localOuter,
+                    remoteOuter,
+                    spi,
+                    useEncap ? encapSocket : null,
+                    factory.getTestRunnable(
+                            transportInTunnelMode,
+                            spi,
+                            localInner,
+                            remoteInner,
+                            localOuter,
+                            remoteOuter,
+                            inTransportTransform,
+                            outTransportTransform,
+                            useEncap ? encapSocket.getPort() : 0,
+                            expectedPacketSize));
+        }
+    }
+
+    private void buildTunnelAndNetwork(
+            InetAddress localInner,
+            InetAddress remoteInner,
+            InetAddress localOuter,
+            InetAddress remoteOuter,
+            int spi,
+            UdpEncapsulationSocket encapSocket,
+            TestRunnable test)
+            throws Exception {
+        int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
+        TestNetworkCallback testNetworkCb = null;
+
+        try (IpSecManager.SecurityParameterIndex inSpi =
+                        mISM.allocateSecurityParameterIndex(localOuter, spi);
+                IpSecManager.SecurityParameterIndex outSpi =
+                        mISM.allocateSecurityParameterIndex(remoteOuter, spi);
                 IpSecManager.IpSecTunnelInterface tunnelIntf =
-                        mISM.createIpSecTunnelInterface(outer, outer, mCM.getActiveNetwork());
-                IpSecManager.UdpEncapsulationSocket encapSocket =
-                        mISM.openUdpEncapsulationSocket()) {
+                        mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
+            // Build the test network
+            tunnelIntf.addAddress(localInner, innerPrefixLen);
+            testNetworkCb = setupAndGetTestNetwork(tunnelIntf.getInterfaceName());
+            Network testNetwork = testNetworkCb.getNetworkBlocking();
 
-            IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(mContext);
+            // Check interface was created
+            NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
+            assertNotNull(netIntf);
+
+            // Verify address was added
+            netIntf = NetworkInterface.getByInetAddress(localInner);
+            assertNotNull(netIntf);
+            assertEquals(tunnelIntf.getInterfaceName(), netIntf.getDisplayName());
+
+            // Configure Transform parameters
+            IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
             transformBuilder.setEncryption(
                     new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
             transformBuilder.setAuthentication(
                     new IpSecAlgorithm(
                             IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
 
-            if (useEncap) {
+            if (encapSocket != null) {
                 transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
             }
 
-            // Check transform application
-            try (IpSecTransform transform = transformBuilder.buildTunnelModeTransform(outer, spi)) {
-                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, transform);
-                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, transform);
+            // Apply transform and check that traffic is properly encrypted
+            try (IpSecTransform inTransform =
+                            transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
+                    IpSecTransform outTransform =
+                            transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
+                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, inTransform);
+                mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, outTransform);
 
-                // TODO: Test to ensure that send/receive works with these transforms.
+                test.run(testNetwork);
             }
 
-            // Check interface was created
-            NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
-            assertNotNull(netIntf);
+            // Teardown the test network
+            sTNM.teardownTestNetwork(testNetwork);
 
-            // Add addresses and check
-            tunnelIntf.addAddress(inner, innerPrefixLen);
-            for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) {
-                assertEquals(intfAddr.getAddress(), inner);
-                assertEquals(intfAddr.getNetworkPrefixLength(), innerPrefixLen);
-            }
-
-            // Remove addresses and check
-            tunnelIntf.removeAddress(inner, innerPrefixLen);
-            assertTrue(netIntf.getInterfaceAddresses().isEmpty());
+            // Remove addresses and check that interface is still present, but fails lookup-by-addr
+            tunnelIntf.removeAddress(localInner, innerPrefixLen);
+            assertNotNull(NetworkInterface.getByName(tunnelIntf.getInterfaceName()));
+            assertNull(NetworkInterface.getByInetAddress(localInner));
 
             // Check interface was cleaned up
             tunnelIntf.close();
-            netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
-            assertNull(netIntf);
+            assertNull(NetworkInterface.getByName(tunnelIntf.getInterfaceName()));
+        } finally {
+            if (testNetworkCb != null) {
+                sCM.unregisterNetworkCallback(testNetworkCb);
+            }
         }
     }
 
-    /*
-     * Create, add and remove addresses, then teardown tunnel
-     */
+    private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
+        byte[] socketResponseBytes = socket.receive();
+        assertArrayEquals(TEST_DATA, socketResponseBytes);
+    }
+
+    private int getRandomSpi(InetAddress localOuter, InetAddress remoteOuter) throws Exception {
+        // Try to allocate both in and out SPIs using the same requested SPI value.
+        try (IpSecManager.SecurityParameterIndex inSpi =
+                        mISM.allocateSecurityParameterIndex(localOuter);
+                IpSecManager.SecurityParameterIndex outSpi =
+                        mISM.allocateSecurityParameterIndex(remoteOuter, inSpi.getSpi()); ) {
+            return inSpi.getSpi();
+        }
+    }
+
+    private IpHeader getIpHeader(int protocol, InetAddress src, InetAddress dst, Payload payload) {
+        if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
+            throw new IllegalArgumentException("Invalid src/dst address combination");
+        }
+
+        if (src instanceof Inet6Address) {
+            return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
+        } else {
+            return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
+        }
+    }
+
+    private EspHeader buildTransportModeEspPacket(
+            int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
+        IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
+
+        return new EspHeader(
+                payload.getProtocolId(),
+                spi,
+                1, // sequence number
+                CRYPT_KEY, // Same key for auth and crypt
+                payload.getPacketBytes(preEspIpHeader));
+    }
+
+    private EspHeader buildTunnelModeEspPacket(
+            int spi,
+            InetAddress srcInner,
+            InetAddress dstInner,
+            InetAddress srcOuter,
+            InetAddress dstOuter,
+            int port,
+            int encapPort,
+            Payload payload)
+            throws Exception {
+        IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
+        return new EspHeader(
+                innerIp.getProtocolId(),
+                spi,
+                1, // sequence number
+                CRYPT_KEY, // Same key for auth and crypt
+                innerIp.getPacketBytes());
+    }
+
+    private IpHeader maybeEncapPacket(
+            InetAddress src, InetAddress dst, int encapPort, EspHeader espPayload)
+            throws Exception {
+
+        Payload payload = espPayload;
+        if (encapPort != 0) {
+            payload = new UdpHeader(encapPort, encapPort, espPayload);
+        }
+
+        return getIpHeader(payload.getProtocolId(), src, dst, payload);
+    }
+
+    private byte[] getTunnelModePacket(
+            int spi,
+            InetAddress srcInner,
+            InetAddress dstInner,
+            InetAddress srcOuter,
+            InetAddress dstOuter,
+            int port,
+            int encapPort)
+            throws Exception {
+        UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
+
+        EspHeader espPayload =
+                buildTunnelModeEspPacket(
+                        spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
+        return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
+    }
+
+    private byte[] getTransportInTunnelModePacket(
+            int spiInner,
+            int spiOuter,
+            InetAddress srcInner,
+            InetAddress dstInner,
+            InetAddress srcOuter,
+            InetAddress dstOuter,
+            int port,
+            int encapPort)
+            throws Exception {
+        UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
+
+        EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
+        espPayload =
+                buildTunnelModeEspPacket(
+                        spiOuter,
+                        srcInner,
+                        dstInner,
+                        srcOuter,
+                        dstOuter,
+                        port,
+                        encapPort,
+                        espPayload);
+        return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
+    }
+
+    // Transport-in-Tunnel mode tests
+    @Test
+    public void testTransportInTunnelModeV4InV4() throws Exception {
+        checkTunnelOutput(AF_INET, AF_INET, false, true);
+        checkTunnelInput(AF_INET, AF_INET, false, true);
+    }
+
+    @Test
+    public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+        checkTunnelOutput(AF_INET, AF_INET, true, true);
+        checkTunnelInput(AF_INET, AF_INET, true, true);
+    }
+
+    @Test
+    public void testTransportInTunnelModeV4InV6() throws Exception {
+        checkTunnelOutput(AF_INET, AF_INET6, false, true);
+        checkTunnelInput(AF_INET, AF_INET6, false, true);
+    }
+
+    @Test
+    public void testTransportInTunnelModeV6InV4() throws Exception {
+        checkTunnelOutput(AF_INET6, AF_INET, false, true);
+        checkTunnelInput(AF_INET6, AF_INET, false, true);
+    }
+
+    @Test
+    public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+        checkTunnelOutput(AF_INET6, AF_INET, true, true);
+        checkTunnelInput(AF_INET6, AF_INET, true, true);
+    }
+
+    @Test
+    public void testTransportInTunnelModeV6InV6() throws Exception {
+        checkTunnelOutput(AF_INET, AF_INET6, false, true);
+        checkTunnelInput(AF_INET, AF_INET6, false, true);
+    }
+
+    // Tunnel mode tests
+    @Test
     public void testTunnelV4InV4() throws Exception {
-        checkTunnel(INNER_ADDR4, OUTER_ADDR4, false);
+        checkTunnelOutput(AF_INET, AF_INET, false, false);
+        checkTunnelInput(AF_INET, AF_INET, false, false);
     }
 
+    @Test
     public void testTunnelV4InV4UdpEncap() throws Exception {
-        checkTunnel(INNER_ADDR4, OUTER_ADDR4, true);
+        checkTunnelOutput(AF_INET, AF_INET, true, false);
+        checkTunnelInput(AF_INET, AF_INET, true, false);
     }
 
+    @Test
     public void testTunnelV4InV6() throws Exception {
-        checkTunnel(INNER_ADDR4, OUTER_ADDR6, false);
+        checkTunnelOutput(AF_INET, AF_INET6, false, false);
+        checkTunnelInput(AF_INET, AF_INET6, false, false);
     }
 
+    @Test
     public void testTunnelV6InV4() throws Exception {
-        checkTunnel(INNER_ADDR6, OUTER_ADDR4, false);
+        checkTunnelOutput(AF_INET6, AF_INET, false, false);
+        checkTunnelInput(AF_INET6, AF_INET, false, false);
     }
 
+    @Test
     public void testTunnelV6InV4UdpEncap() throws Exception {
-        checkTunnel(INNER_ADDR6, OUTER_ADDR4, true);
+        checkTunnelOutput(AF_INET6, AF_INET, true, false);
+        checkTunnelInput(AF_INET6, AF_INET, true, false);
     }
 
+    @Test
     public void testTunnelV6InV6() throws Exception {
-        checkTunnel(INNER_ADDR6, OUTER_ADDR6, false);
+        checkTunnelOutput(AF_INET6, AF_INET6, false, false);
+        checkTunnelInput(AF_INET6, AF_INET6, false, false);
     }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
index e0c03a1..cb0cda8 100644
--- a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.platform.test.annotations.AppModeFull;
 import android.os.FileUtils;
 
 import androidx.test.InstrumentationRegistry;
@@ -99,6 +100,7 @@
      * returns the hash of config we set.
      */
     @Test
+    @AppModeFull(reason = "Cannot access resource file in instant app mode")
     public void testGetWatchlistConfigHash() throws Exception {
         // Set watchlist config file for test
         setWatchlistConfig(TEST_WATCHLIST_XML);
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
new file mode 100644
index 0000000..6177827
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PacketUtils {
+    private static final String TAG = PacketUtils.class.getSimpleName();
+
+    private static final int DATA_BUFFER_LEN = 4096;
+
+    static final int IP4_HDRLEN = 20;
+    static final int IP6_HDRLEN = 40;
+    static final int UDP_HDRLEN = 8;
+    static final int TCP_HDRLEN = 20;
+    static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
+
+    // Not defined in OsConstants
+    static final int IPPROTO_IPV4 = 4;
+    static final int IPPROTO_ESP = 50;
+
+    // Encryption parameters
+    static final int AES_GCM_IV_LEN = 8;
+    static final int AES_CBC_IV_LEN = 16;
+    static final int AES_GCM_BLK_SIZE = 4;
+    static final int AES_CBC_BLK_SIZE = 16;
+
+    // Encryption algorithms
+    static final String AES = "AES";
+    static final String AES_CBC = "AES/CBC/NoPadding";
+    static final String HMAC_SHA_256 = "HmacSHA256";
+
+    public interface Payload {
+        byte[] getPacketBytes(IpHeader header) throws Exception;
+
+        void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception;
+
+        short length();
+
+        int getProtocolId();
+    }
+
+    public abstract static class IpHeader {
+
+        public final byte proto;
+        public final InetAddress srcAddr;
+        public final InetAddress dstAddr;
+        public final Payload payload;
+
+        public IpHeader(int proto, InetAddress src, InetAddress dst, Payload payload) {
+            this.proto = (byte) proto;
+            this.srcAddr = src;
+            this.dstAddr = dst;
+            this.payload = payload;
+        }
+
+        public abstract byte[] getPacketBytes() throws Exception;
+
+        public abstract int getProtocolId();
+    }
+
+    public static class Ip4Header extends IpHeader {
+        private short checksum;
+
+        public Ip4Header(int proto, Inet4Address src, Inet4Address dst, Payload payload) {
+            super(proto, src, dst, payload);
+        }
+
+        public byte[] getPacketBytes() throws Exception {
+            ByteBuffer resultBuffer = buildHeader();
+            payload.addPacketBytes(this, resultBuffer);
+
+            return getByteArrayFromBuffer(resultBuffer);
+        }
+
+        public ByteBuffer buildHeader() {
+            ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+            // Version, IHL
+            bb.put((byte) (0x45));
+
+            // DCSP, ECN
+            bb.put((byte) 0);
+
+            // Total Length
+            bb.putShort((short) (IP4_HDRLEN + payload.length()));
+
+            // Empty for Identification, Flags and Fragment Offset
+            bb.putShort((short) 0);
+            bb.put((byte) 0x40);
+            bb.put((byte) 0x00);
+
+            // TTL
+            bb.put((byte) 64);
+
+            // Protocol
+            bb.put(proto);
+
+            // Header Checksum
+            final int ipChecksumOffset = bb.position();
+            bb.putShort((short) 0);
+
+            // Src/Dst addresses
+            bb.put(srcAddr.getAddress());
+            bb.put(dstAddr.getAddress());
+
+            bb.putShort(ipChecksumOffset, calculateChecksum(bb));
+
+            return bb;
+        }
+
+        private short calculateChecksum(ByteBuffer bb) {
+            int checksum = 0;
+
+            // Calculate sum of 16-bit values, excluding checksum. IPv4 headers are always 32-bit
+            // aligned, so no special cases needed for unaligned values.
+            ShortBuffer shortBuffer = ByteBuffer.wrap(getByteArrayFromBuffer(bb)).asShortBuffer();
+            while (shortBuffer.hasRemaining()) {
+                short val = shortBuffer.get();
+
+                // Wrap as needed
+                checksum = addAndWrapForChecksum(checksum, val);
+            }
+
+            return onesComplement(checksum);
+        }
+
+        public int getProtocolId() {
+            return IPPROTO_IPV4;
+        }
+    }
+
+    public static class Ip6Header extends IpHeader {
+        public Ip6Header(int nextHeader, Inet6Address src, Inet6Address dst, Payload payload) {
+            super(nextHeader, src, dst, payload);
+        }
+
+        public byte[] getPacketBytes() throws Exception {
+            ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+            // Version | Traffic Class (First 4 bits)
+            bb.put((byte) 0x60);
+
+            // Traffic class (Last 4 bits), Flow Label
+            bb.put((byte) 0);
+            bb.put((byte) 0);
+            bb.put((byte) 0);
+
+            // Payload Length
+            bb.putShort((short) payload.length());
+
+            // Next Header
+            bb.put(proto);
+
+            // Hop Limit
+            bb.put((byte) 64);
+
+            // Src/Dst addresses
+            bb.put(srcAddr.getAddress());
+            bb.put(dstAddr.getAddress());
+
+            // Payload
+            payload.addPacketBytes(this, bb);
+
+            return getByteArrayFromBuffer(bb);
+        }
+
+        public int getProtocolId() {
+            return IPPROTO_IPV6;
+        }
+    }
+
+    public static class BytePayload implements Payload {
+        public final byte[] payload;
+
+        public BytePayload(byte[] payload) {
+            this.payload = payload;
+        }
+
+        public int getProtocolId() {
+            return -1;
+        }
+
+        public byte[] getPacketBytes(IpHeader header) {
+            ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+            addPacketBytes(header, bb);
+            return getByteArrayFromBuffer(bb);
+        }
+
+        public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) {
+            resultBuffer.put(payload);
+        }
+
+        public short length() {
+            return (short) payload.length;
+        }
+    }
+
+    public static class UdpHeader implements Payload {
+
+        public final short srcPort;
+        public final short dstPort;
+        public final Payload payload;
+
+        public UdpHeader(int srcPort, int dstPort, Payload payload) {
+            this.srcPort = (short) srcPort;
+            this.dstPort = (short) dstPort;
+            this.payload = payload;
+        }
+
+        public int getProtocolId() {
+            return IPPROTO_UDP;
+        }
+
+        public short length() {
+            return (short) (payload.length() + 8);
+        }
+
+        public byte[] getPacketBytes(IpHeader header) throws Exception {
+            ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+            addPacketBytes(header, bb);
+            return getByteArrayFromBuffer(bb);
+        }
+
+        public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+            // Source, Destination port
+            resultBuffer.putShort(srcPort);
+            resultBuffer.putShort(dstPort);
+
+            // Payload Length
+            resultBuffer.putShort(length());
+
+            // Get payload bytes for checksum + payload
+            ByteBuffer payloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+            payload.addPacketBytes(header, payloadBuffer);
+            byte[] payloadBytes = getByteArrayFromBuffer(payloadBuffer);
+
+            // Checksum
+            resultBuffer.putShort(calculateChecksum(header, payloadBytes));
+
+            // Payload
+            resultBuffer.put(payloadBytes);
+        }
+
+        private short calculateChecksum(IpHeader header, byte[] payloadBytes) throws Exception {
+            int newChecksum = 0;
+            ShortBuffer srcBuffer = ByteBuffer.wrap(header.srcAddr.getAddress()).asShortBuffer();
+            ShortBuffer dstBuffer = ByteBuffer.wrap(header.dstAddr.getAddress()).asShortBuffer();
+
+            while (srcBuffer.hasRemaining() || dstBuffer.hasRemaining()) {
+                short val = srcBuffer.hasRemaining() ? srcBuffer.get() : dstBuffer.get();
+
+                // Wrap as needed
+                newChecksum = addAndWrapForChecksum(newChecksum, val);
+            }
+
+            // Add pseudo-header values. Proto is 0-padded, so just use the byte.
+            newChecksum = addAndWrapForChecksum(newChecksum, header.proto);
+            newChecksum = addAndWrapForChecksum(newChecksum, length());
+            newChecksum = addAndWrapForChecksum(newChecksum, srcPort);
+            newChecksum = addAndWrapForChecksum(newChecksum, dstPort);
+            newChecksum = addAndWrapForChecksum(newChecksum, length());
+
+            ShortBuffer payloadShortBuffer = ByteBuffer.wrap(payloadBytes).asShortBuffer();
+            while (payloadShortBuffer.hasRemaining()) {
+                newChecksum = addAndWrapForChecksum(newChecksum, payloadShortBuffer.get());
+            }
+            if (payload.length() % 2 != 0) {
+                newChecksum =
+                        addAndWrapForChecksum(
+                                newChecksum, (payloadBytes[payloadBytes.length - 1] << 8));
+            }
+
+            return onesComplement(newChecksum);
+        }
+    }
+
+    public static class EspHeader implements Payload {
+        public final int nextHeader;
+        public final int spi;
+        public final int seqNum;
+        public final byte[] key;
+        public final byte[] payload;
+
+        /**
+         * Generic constructor for ESP headers.
+         *
+         * <p>For Tunnel mode, payload will be a full IP header + attached payloads
+         *
+         * <p>For Transport mode, payload will be only the attached payloads, but with the checksum
+         * calculated using the pre-encryption IP header
+         */
+        public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) {
+            this.nextHeader = nextHeader;
+            this.spi = spi;
+            this.seqNum = seqNum;
+            this.key = key;
+            this.payload = payload;
+        }
+
+        public int getProtocolId() {
+            return IPPROTO_ESP;
+        }
+
+        public short length() {
+            // ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len)
+            return (short)
+                    calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128);
+        }
+
+        public byte[] getPacketBytes(IpHeader header) throws Exception {
+            ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+            addPacketBytes(header, bb);
+            return getByteArrayFromBuffer(bb);
+        }
+
+        public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+            ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+            espPayloadBuffer.putInt(spi);
+            espPayloadBuffer.putInt(seqNum);
+            espPayloadBuffer.put(getCiphertext(key));
+
+            espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16);
+            resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer));
+        }
+
+        private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+            Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256);
+            SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
+            sha256HMAC.init(authKey);
+
+            return sha256HMAC.doFinal(authenticatedSection);
+        }
+
+        /**
+         * Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks
+         *
+         * <p>The ciphertext does NOT include the SPI/Sequence numbers, or the ICV.
+         */
+        private byte[] getCiphertext(byte[] key) throws GeneralSecurityException {
+            int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE);
+            ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
+            paddedPayload.put(payload);
+
+            // Add padding - consecutive integers from 0x01
+            int pad = 1;
+            while (paddedPayload.position() < paddedPayload.limit()) {
+                paddedPayload.put((byte) pad++);
+            }
+
+            paddedPayload.position(paddedPayload.limit() - 2);
+            paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length
+            paddedPayload.put((byte) nextHeader);
+
+            // Generate Initialization Vector
+            byte[] iv = new byte[AES_CBC_IV_LEN];
+            new SecureRandom().nextBytes(iv);
+            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+            SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
+
+            // Encrypt payload
+            Cipher cipher = Cipher.getInstance(AES_CBC);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+            byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload));
+
+            // Build ciphertext
+            ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length);
+            cipherText.put(iv);
+            cipherText.put(encrypted);
+
+            return getByteArrayFromBuffer(cipherText);
+        }
+    }
+
+    private static int addAndWrapForChecksum(int currentChecksum, int value) {
+        currentChecksum += value & 0x0000ffff;
+
+        // Wrap anything beyond the first 16 bits, and add to lower order bits
+        return (currentChecksum >>> 16) + (currentChecksum & 0x0000ffff);
+    }
+
+    private static short onesComplement(int val) {
+        val = (val >>> 16) + (val & 0xffff);
+
+        if (val == 0) return 0;
+        return (short) ((~val) & 0xffff);
+    }
+
+    public static int calculateEspPacketSize(
+            int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
+        final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
+        final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
+        payloadLen += cryptIvLength; // Initialization Vector
+
+        // Align to block size of encryption algorithm
+        payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize);
+        return payloadLen + ESP_HDRLEN + ICV_LEN;
+    }
+
+    private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) {
+        payloadLen += 2; // ESP trailer
+
+        // Align to block size of encryption algorithm
+        return payloadLen + calculateEspPadLen(payloadLen, cryptBlockSize);
+    }
+
+    private static int calculateEspPadLen(int payloadLen, int cryptBlockSize) {
+        return (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize;
+    }
+
+    private static byte[] getByteArrayFromBuffer(ByteBuffer buffer) {
+        return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
+    }
+
+    /*
+     * Debug printing
+     */
+    private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+    public static String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(hexArray[b >>> 4]);
+            sb.append(hexArray[b & 0x0F]);
+            sb.append(' ');
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index 60ac226..5250450 100644
--- a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -24,6 +24,7 @@
 import javax.net.ssl.SSLPeerUnverifiedException;
 
 import android.net.SSLCertificateSocketFactory;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import libcore.javax.net.ssl.SSLConfigurationAsserts;
@@ -101,6 +102,7 @@
      *
      * NOTE: Test will fail if external server is not available.
      */
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void test_createSocket_simple() throws Exception {
         try {
             mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
@@ -117,6 +119,7 @@
      *
      * NOTE: Test will fail if external server is not available.
      */
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void test_createSocket_wrapping() throws Exception {
         try {
             Socket underlying = new Socket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
@@ -135,6 +138,7 @@
      *
      * NOTE: Test will fail if external server is not available.
      */
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void test_createSocket_bind() throws Exception {
         try {
             mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, null, 0);
diff --git a/tests/cts/net/src/android/net/cts/TheaterModeTest.java b/tests/cts/net/src/android/net/cts/TheaterModeTest.java
index 10fca6f..d1ddeaa 100644
--- a/tests/cts/net/src/android/net/cts/TheaterModeTest.java
+++ b/tests/cts/net/src/android/net/cts/TheaterModeTest.java
@@ -18,12 +18,11 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import java.lang.Thread;
-
 public class TheaterModeTest extends AndroidTestCase {
     private static final String TAG = "TheaterModeTest";
     private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
@@ -40,6 +39,7 @@
                        || mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI));
     }
 
+    @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
     public void testTheaterMode() {
         setup();
         if (!mHasFeature) {
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index 503ba51..5bd1e20 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -21,6 +21,7 @@
 import android.net.TrafficStats;
 import android.os.Process;
 import android.os.SystemProperties;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -81,6 +82,7 @@
         return packetCount * (20 + 32 + bytes);
     }
 
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testTrafficStatsForLocalhost() throws IOException {
         final long mobileTxPacketsBefore = TrafficStats.getMobileTxPackets();
         final long mobileRxPacketsBefore = TrafficStats.getMobileRxPackets();
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
new file mode 100644
index 0000000..a030713
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.IPPROTO_ESP;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+public class TunUtils {
+    private static final String TAG = TunUtils.class.getSimpleName();
+
+    private static final int DATA_BUFFER_LEN = 4096;
+    private static final int TIMEOUT = 100;
+
+    private static final int IP4_PROTO_OFFSET = 9;
+    private static final int IP6_PROTO_OFFSET = 6;
+
+    private static final int IP4_ADDR_OFFSET = 12;
+    private static final int IP4_ADDR_LEN = 4;
+    private static final int IP6_ADDR_OFFSET = 8;
+    private static final int IP6_ADDR_LEN = 16;
+
+    private final ParcelFileDescriptor mTunFd;
+    private final List<byte[]> mPackets = new ArrayList<>();
+    private final Thread mReaderThread;
+
+    public TunUtils(ParcelFileDescriptor tunFd) {
+        mTunFd = tunFd;
+
+        // Start background reader thread
+        mReaderThread =
+                new Thread(
+                        () -> {
+                            try {
+                                // Loop will exit and thread will quit when tunFd is closed.
+                                // Receiving either EOF or an exception will exit this reader loop.
+                                // FileInputStream in uninterruptable, so there's no good way to
+                                // ensure that this thread shuts down except upon FD closure.
+                                while (true) {
+                                    byte[] intercepted = receiveFromTun();
+                                    if (intercepted == null) {
+                                        // Exit once we've hit EOF
+                                        return;
+                                    } else if (intercepted.length > 0) {
+                                        // Only save packet if we've received any bytes.
+                                        synchronized (mPackets) {
+                                            mPackets.add(intercepted);
+                                            mPackets.notifyAll();
+                                        }
+                                    }
+                                }
+                            } catch (IOException ignored) {
+                                // Simply exit this reader thread
+                                return;
+                            }
+                        });
+        mReaderThread.start();
+    }
+
+    private byte[] receiveFromTun() throws IOException {
+        FileInputStream in = new FileInputStream(mTunFd.getFileDescriptor());
+        byte[] inBytes = new byte[DATA_BUFFER_LEN];
+        int bytesRead = in.read(inBytes);
+
+        if (bytesRead < 0) {
+            return null; // return null for EOF
+        } else if (bytesRead >= DATA_BUFFER_LEN) {
+            throw new IllegalStateException("Too big packet. Fragmentation unsupported");
+        }
+        return Arrays.copyOf(inBytes, bytesRead);
+    }
+
+    private byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
+        synchronized (mPackets) {
+            for (int i = startIndex; i < mPackets.size(); i++) {
+                byte[] pkt = mPackets.get(i);
+                if (verifier.test(pkt)) {
+                    return pkt;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Checks if the specified bytes were ever sent in plaintext.
+     *
+     * <p>Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like
+     *
+     * @param plaintext the plaintext bytes to check for
+     * @param startIndex the index in the list to check for
+     */
+    public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) {
+        Predicate<byte[]> verifier =
+                (pkt) -> {
+                    return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext))
+                            != -1;
+                };
+        return getFirstMatchingPacket(verifier, startIndex) != null;
+    }
+
+    public byte[] getEspPacket(int spi, boolean encap, int startIndex) {
+        return getFirstMatchingPacket(
+                (pkt) -> {
+                    return isEsp(pkt, spi, encap);
+                },
+                startIndex);
+    }
+
+    public byte[] awaitEspPacketNoPlaintext(
+            int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+        long endTime = System.currentTimeMillis() + TIMEOUT;
+        int startIndex = 0;
+
+        synchronized (mPackets) {
+            while (System.currentTimeMillis() < endTime) {
+                byte[] espPkt = getEspPacket(spi, useEncap, startIndex);
+                if (espPkt != null) {
+                    // Validate packet size
+                    assertEquals(expectedPacketSize, espPkt.length);
+
+                    // Always check plaintext from start
+                    assertFalse(hasPlaintextPacket(plaintext, 0));
+                    return espPkt; // We've found the packet we're looking for.
+                }
+
+                startIndex = mPackets.size();
+
+                // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
+                long waitTimeout = endTime - System.currentTimeMillis();
+                if (waitTimeout > 0) {
+                    mPackets.wait(waitTimeout);
+                }
+            }
+
+            fail("No such ESP packet found with SPI " + spi);
+        }
+        return null;
+    }
+
+    private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
+        // Check SPI byte by byte.
+        return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
+                && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
+                && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
+                && pkt[espOffset + 3] == (byte) (spi & 0xff);
+    }
+
+    private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
+        if (isIpv6(pkt)) {
+            // IPv6 UDP encap not supported by kernels; assume non-encap.
+            return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+        } else {
+            // Use default IPv4 header length (assuming no options)
+            if (encap) {
+                return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP
+                        && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi);
+            } else {
+                return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi);
+            }
+        }
+    }
+
+    private static boolean isIpv6(byte[] pkt) {
+        // First nibble shows IP version. 0x60 for IPv6
+        return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
+    }
+
+    private static byte[] getReflectedPacket(byte[] pkt) {
+        byte[] reflected = Arrays.copyOf(pkt, pkt.length);
+
+        if (isIpv6(pkt)) {
+            // Set reflected packet's dst to that of the original's src
+            System.arraycopy(
+                    pkt, // src
+                    IP6_ADDR_OFFSET + IP6_ADDR_LEN, // src offset
+                    reflected, // dst
+                    IP6_ADDR_OFFSET, // dst offset
+                    IP6_ADDR_LEN); // len
+            // Set reflected packet's src IP to that of the original's dst IP
+            System.arraycopy(
+                    pkt, // src
+                    IP6_ADDR_OFFSET, // src offset
+                    reflected, // dst
+                    IP6_ADDR_OFFSET + IP6_ADDR_LEN, // dst offset
+                    IP6_ADDR_LEN); // len
+        } else {
+            // Set reflected packet's dst to that of the original's src
+            System.arraycopy(
+                    pkt, // src
+                    IP4_ADDR_OFFSET + IP4_ADDR_LEN, // src offset
+                    reflected, // dst
+                    IP4_ADDR_OFFSET, // dst offset
+                    IP4_ADDR_LEN); // len
+            // Set reflected packet's src IP to that of the original's dst IP
+            System.arraycopy(
+                    pkt, // src
+                    IP4_ADDR_OFFSET, // src offset
+                    reflected, // dst
+                    IP4_ADDR_OFFSET + IP4_ADDR_LEN, // dst offset
+                    IP4_ADDR_LEN); // len
+        }
+        return reflected;
+    }
+
+    /** Takes all captured packets, flips the src/dst, and re-injects them. */
+    public void reflectPackets() throws IOException {
+        synchronized (mPackets) {
+            for (byte[] pkt : mPackets) {
+                injectPacket(getReflectedPacket(pkt));
+            }
+        }
+    }
+
+    public void injectPacket(byte[] pkt) throws IOException {
+        FileOutputStream out = new FileOutputStream(mTunFd.getFileDescriptor());
+        out.write(pkt);
+        out.flush();
+    }
+
+    /** Resets the intercepted packets. */
+    public void reset() throws IOException {
+        synchronized (mPackets) {
+            mPackets.clear();
+        }
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
index 7076ea2..2d615bb 100644
--- a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
+++ b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
@@ -170,6 +170,12 @@
         String initialPercentSign = "title=%B5";
         assertEquals(expectedPlus, uqs.unescape(initialPlus));
         assertEquals(expectedPercentSignHex, uqs.unescape(initialPercentSign));
+        String expectedPlusThenPercentSign = "Joe Random, User";
+        String plusThenPercentSign = "Joe+Random%2C%20User";
+        assertEquals(expectedPlusThenPercentSign, uqs.unescape(plusThenPercentSign));
+        String expectedPercentSignThenPlus = "Joe, Random User";
+        String percentSignThenPlus = "Joe%2C+Random+User";
+        assertEquals(expectedPercentSignThenPlus, uqs.unescape(percentSignThenPlus));
 
         assertTrue(uqs.decodeHexDigit('0') >= 0);
         assertTrue(uqs.decodeHexDigit('b') >= 0);
diff --git a/tests/cts/net/src/android/net/cts/VpnServiceTest.java b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
index 8bdd7b0..15af23c 100644
--- a/tests/cts/net/src/android/net/cts/VpnServiceTest.java
+++ b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
@@ -18,6 +18,7 @@
 import android.content.Intent;
 import android.net.VpnService;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import java.io.File;
@@ -35,6 +36,7 @@
 
     private VpnService mVpnService = new VpnService();
 
+    @AppModeFull(reason = "PackageManager#queryIntentActivities cannot access in instant app mode")
     public void testPrepare() throws Exception {
         // Should never return null since we are not prepared.
         Intent intent = VpnService.prepare(mContext);
@@ -60,6 +62,7 @@
         }
     }
 
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testProtect_DatagramSocket() throws Exception {
         DatagramSocket socket = new DatagramSocket();
         try {
@@ -88,6 +91,7 @@
         }
     }
 
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testProtect_int() throws Exception {
         DatagramSocket socket = new DatagramSocket();
         ParcelFileDescriptor descriptor = ParcelFileDescriptor.fromDatagramSocket(socket);
diff --git a/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java b/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
index 2e5306d..8d7dff0 100644
--- a/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
+++ b/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
@@ -22,6 +22,7 @@
 import org.apache.http.impl.client.DefaultHttpClient;
 
 import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.webkit.cts.CtsTestServer;
 
@@ -30,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
 public class ApacheHttpClientTest extends AndroidTestCase {
 
     private static final int NUM_DOWNLOADS = 20;
diff --git a/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java b/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
index 198f973..354954e 100644
--- a/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
+++ b/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
@@ -22,6 +22,7 @@
 import junit.framework.TestCase;
 
 import android.net.http.HttpResponseCache;
+import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.util.FileUtils;
 
@@ -126,6 +127,7 @@
      * Make sure that statistics tracking are wired all the way through the
      * wrapper class. http://code.google.com/p/android/issues/detail?id=25418
      */
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
     public void testStatisticsTracking() throws Exception {
         HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024);
 
diff --git a/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java b/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
index f06d7e9..1bd7fad 100644
--- a/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
+++ b/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
@@ -21,13 +21,14 @@
 import android.net.rtp.AudioGroup;
 import android.net.rtp.AudioStream;
 import android.net.rtp.RtpStream;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
-import android.util.Log;
 
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 
+@AppModeFull(reason = "RtpStream cannot create in instant app mode")
 public class AudioGroupTest extends AndroidTestCase {
 
     private static final String TAG = AudioGroupTest.class.getSimpleName();
diff --git a/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java b/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
index 323b022..f2db6ee 100644
--- a/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
+++ b/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
@@ -17,10 +17,12 @@
 
 import android.net.rtp.AudioCodec;
 import android.net.rtp.AudioStream;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import java.net.InetAddress;
 
+@AppModeFull(reason = "RtpStream cannot create in instant app mode")
 public class AudioStreamTest extends AndroidTestCase {
 
     private void testRtpStream(InetAddress address) throws Exception {
diff --git a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 389621e..1901f02 100644
--- a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -40,6 +40,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.SystemUtil;
@@ -56,6 +57,7 @@
  * Wi-Fi Aware CTS test suite: single device testing. Performs tests on a single
  * device to validate Wi-Fi Aware.
  */
+@AppModeFull(reason = "Cannot get WifiAwareManager in instant app mode")
 public class SingleDeviceTest extends AndroidTestCase {
     private static final String TAG = "WifiAwareCtsTests";
 
diff --git a/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
index c80e372..628571c 100644
--- a/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -32,6 +32,7 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pManager;
 import android.provider.Settings;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -45,6 +46,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class ConcurrencyTest extends AndroidTestCase {
     private class MySync {
         static final int WIFI_STATE = 0;
diff --git a/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java b/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java
index 54fe9c7..71f04a3 100644
--- a/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.MulticastLock;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class MulticastLockTest extends AndroidTestCase {
 
     private static final String WIFI_TAG = "MulticastLockTest";
diff --git a/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
index 2e2e75b..f2a2b48 100644
--- a/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -29,6 +30,7 @@
 import java.util.List;
 import java.util.ArrayList;
 
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
 public class NsdManagerTest extends AndroidTestCase {
 
     private static final String TAG = "NsdManagerTest";
diff --git a/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java b/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
index 836df61..ccf5fe2 100644
--- a/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
@@ -25,11 +25,13 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import com.android.compatibility.common.util.SystemUtil;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class ScanResultTest extends AndroidTestCase {
     private static class MySync {
         int expectedState = STATE_NULL;
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
index 4480a24..a59c85e 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -21,8 +21,10 @@
 import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiConfigurationTest extends AndroidTestCase {
     private  WifiManager mWifiManager;
     @Override
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index 0fce1ee..b592c10 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -22,6 +22,7 @@
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
 import android.net.wifi.WifiManager;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.SystemUtil;
@@ -33,6 +34,7 @@
 import java.security.cert.X509Certificate;
 import java.security.spec.PKCS8EncodedKeySpec;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiEnterpriseConfigTest extends AndroidTestCase {
     private WifiManager mWifiManager;
 
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
index 77598ed..5367722 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
@@ -26,6 +26,7 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
 import android.net.wifi.WifiSsid;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.PollingCheck;
@@ -33,6 +34,7 @@
 
 import java.util.concurrent.Callable;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiInfoTest extends AndroidTestCase {
     private static class MySync {
         int expectedState = STATE_NULL;
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
index 0703e60..6ac92d4 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
+import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiLockTest extends AndroidTestCase {
 
     private static final String WIFI_TAG = "WifiLockTest";
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 1d66682..d8c7dc8 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -36,9 +36,11 @@
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Process;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
 import android.test.AndroidTestCase;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -56,6 +58,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiManagerTest extends AndroidTestCase {
     private static class MySync {
         int expectedState = STATE_NULL;
@@ -744,21 +747,16 @@
         allowedUIDs.add(Process.SHELL_UID);
         allowedUIDs.add(Process.PHONE_UID);
         allowedUIDs.add(Process.NETWORK_STACK_UID);
+        allowedUIDs.add(Process.NFC_UID);
 
         // only quick settings is allowed to bind to the BIND_QUICK_SETTINGS_TILE permission, using
-        // this fact to determined allowed package name for sysui
-        String validPkg = "";
-        final List<PackageInfo> sysuiPackage = pm.getPackagesHoldingPermissions(new String[] {
+        // this fact to determined allowed package name for sysui. This is a signature permission,
+        // so allow any package with this permission.
+        final List<PackageInfo> sysuiPackages = pm.getPackagesHoldingPermissions(new String[] {
                 android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
         }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
-
-        if (sysuiPackage.size() > 1) {
-            fail("The BIND_QUICK_SETTINGS_TILE permission must only be held by one package");
-        }
-
-        if (sysuiPackage.size() == 1) {
-            validPkg = sysuiPackage.get(0).packageName;
-            allowedPackages.add(validPkg);
+        for (PackageInfo info : sysuiPackages) {
+            allowedPackages.add(info.packageName);
         }
 
         // the captive portal flow also currently holds the NETWORK_SETTINGS permission
@@ -801,6 +799,8 @@
      * functionality.  The permission is intended to be granted to only the device setup wizard.
      */
     public void testNetworkSetupWizardPermission() {
+        final ArraySet<String> allowedPackages = new ArraySet();
+
         final PackageManager pm = getContext().getPackageManager();
 
         final Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -808,16 +808,39 @@
         final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
         String validPkg = "";
         if (ri != null) {
+            allowedPackages.add(ri.activityInfo.packageName);
             validPkg = ri.activityInfo.packageName;
         }
 
-        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
-                android.Manifest.permission.NETWORK_SETUP_WIZARD
+        final Intent preIntent = new Intent("com.android.setupwizard.OEM_PRE_SETUP");
+        preIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        final ResolveInfo preRi = pm
+            .resolveActivity(preIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
+        String prePackageName = "";
+        if (null != preRi) {
+            prePackageName = preRi.activityInfo.packageName;
+        }
+
+        final Intent postIntent = new Intent("com.android.setupwizard.OEM_POST_SETUP");
+        postIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        final ResolveInfo postRi = pm
+            .resolveActivity(postIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
+        String postPackageName = "";
+        if (null != postRi) {
+            postPackageName = postRi.activityInfo.packageName;
+        }
+        if (!TextUtils.isEmpty(prePackageName) && !TextUtils.isEmpty(postPackageName)
+            && prePackageName.equals(postPackageName)) {
+            allowedPackages.add(prePackageName);
+        }
+
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+            android.Manifest.permission.NETWORK_SETUP_WIZARD
         }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
         for (PackageInfo pi : holding) {
-            if (!Objects.equals(pi.packageName, validPkg)) {
+            if (!allowedPackages.contains(pi.packageName)) {
                 fail("The NETWORK_SETUP_WIZARD permission must not be held by " + pi.packageName
-                    + " and must be revoked for security reasons [" + validPkg +"]");
+                    + " and must be revoked for security reasons [" + validPkg + "]");
             }
         }
     }
diff --git a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
index 20791c0..f71cadb 100644
--- a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -19,6 +19,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
+import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
@@ -31,6 +32,7 @@
 /**
  * Wi-Fi RTT CTS test: range to all available Access Points which support IEEE 802.11mc.
  */
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class WifiRttTest extends TestBase {
     // Number of scans to do while searching for APs supporting IEEE 802.11mc
     private static final int NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP = 2;