Merge "Add tests for in-kernel VPN/VCN dataplane"
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index bd7ebda..88c885a 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -67,6 +67,20 @@
     name: "com.android.tethering-bootclasspath-fragment",
     contents: ["framework-tethering"],
     apex_available: ["com.android.tethering"],
+
+    // The bootclasspath_fragments that provide APIs on which this depends.
+    fragments: [
+        {
+            apex: "com.android.art",
+            module: "art-bootclasspath-fragment",
+        },
+    ],
+
+    // Additional stubs libraries that this fragment's contents use which are
+    // not provided by another bootclasspath_fragment.
+    additional_stubs: [
+        "android-non-updatable",
+    ],
 }
 
 override_apex {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 56dc69c..2c1fd29 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -1069,10 +1069,11 @@
             throw new AssertionError("IP address array not valid IPv4 address!");
         }
 
+        final String protoStr = (key.l4proto == OsConstants.IPPROTO_TCP) ? "tcp" : "udp";
         final String ageStr = (value.lastUsed == 0) ? "-"
                 : String.format("%dms", (now - value.lastUsed) / 1_000_000);
-        return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
-                key.dstMac, key.iif, getIfName(key.iif), src4, key.srcPort,
+        return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
+                protoStr, key.dstMac, key.iif, getIfName(key.iif), src4, key.srcPort,
                 value.oif, getIfName(value.oif),
                 public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageStr);
     }
@@ -1095,12 +1096,14 @@
 
         try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
                 BpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
-            pw.println("IPv4 Upstream: [inDstMac] iif(iface) src -> nat -> dst [outDstMac] age");
+            pw.println("IPv4 Upstream: proto [inDstMac] iif(iface) src -> nat -> "
+                    + "dst [outDstMac] age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, UPSTREAM, upstreamMap, pw);
             pw.decreaseIndent();
 
-            pw.println("IPv4 Downstream: [inDstMac] iif(iface) src -> nat -> dst [outDstMac] age");
+            pw.println("IPv4 Downstream: proto [inDstMac] iif(iface) src -> nat -> "
+                    + "dst [outDstMac] age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
             pw.decreaseIndent();
diff --git a/Tethering/tests/Android.bp b/Tethering/tests/Android.bp
index 8f31c57..72ca666 100644
--- a/Tethering/tests/Android.bp
+++ b/Tethering/tests/Android.bp
@@ -22,7 +22,7 @@
     name: "TetheringTestsJarJarRules",
     srcs: ["jarjar-rules.txt"],
     visibility: [
-        "//frameworks/base/packages/Tethering/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
     ]
 }
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index e807613..b93a969 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -78,9 +78,27 @@
     compile_multilib: "both",
 }
 
+android_library {
+    name: "TetheringCoverageTestsLib",
+    min_sdk_version: "30",
+    static_libs: [
+        "NetdStaticLibTestsLib",
+        "NetworkStaticLibTestsLib",
+        "NetworkStackTestsLib",
+        "TetheringTestsLatestSdkLib",
+        "TetheringIntegrationTestsLatestSdkLib",
+    ],
+    jarjar_rules: ":TetheringTestsJarJarRules",
+    manifest: "AndroidManifest_coverage.xml",
+    visibility: [
+        "//packages/modules/Connectivity/tests:__subpackages__"
+    ],
+}
+
 // Special version of the tethering tests that includes all tests necessary for code coverage
 // purposes. This is currently the union of TetheringTests, TetheringIntegrationTests and
 // NetworkStackTests.
+// TODO: remove in favor of ConnectivityCoverageTests, which includes below tests and more
 android_test {
     name: "TetheringCoverageTests",
     platform_apis: true,
@@ -91,11 +109,7 @@
     defaults: ["libnetworkstackutilsjni_deps"],
     static_libs: [
         "modules-utils-native-coverage-listener",
-        "NetdStaticLibTestsLib",
-        "NetworkStaticLibTestsLib",
-        "NetworkStackTestsLib",
-        "TetheringTestsLatestSdkLib",
-        "TetheringIntegrationTestsLatestSdkLib",
+        "TetheringCoverageTestsLib",
     ],
     jni_libs: [
         // For mockito extended
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 192a540..f6e29cd 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -75,7 +75,6 @@
         "libstaticjvmtiagent",
         "libtetherutilsjni",
     ],
-    jarjar_rules: ":TetheringTestsJarJarRules",
 }
 
 // Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -100,4 +99,5 @@
     ],
     defaults: ["TetheringTestsDefaults"],
     compile_multilib: "both",
+    jarjar_rules: ":TetheringTestsJarJarRules",
 }
diff --git a/framework/Android.bp b/framework/Android.bp
index 93ef3bf..ee3a5d5 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -97,8 +97,6 @@
         ],
     },
     impl_only_libs: [
-        // TODO (b/183097033) remove once module_current includes core_platform
-        "stable.core.platform.api.stubs",
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
         "net-utils-device-common",
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7e2f688..14ec608 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -4939,7 +4939,7 @@
                 Log.e(TAG, "Can't set proxy properties", e);
             }
             // Must flush DNS cache as new network may have different DNS resolutions.
-            InetAddressCompat.clearDnsCache();
+            InetAddress.clearDnsCache();
             // Must flush socket pool as idle sockets will be bound to previous network and may
             // cause subsequent fetches to be performed on old network.
             NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 085de6b..8fc0065 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -29,6 +29,8 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.net.ConnectivityManager.MultipathPreference;
+import android.os.Binder;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -1039,6 +1041,15 @@
         return getUidSetFromString(uidList);
     }
 
+    private static boolean isCallingFromSystem() {
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+        if (uid == Process.SYSTEM_UID && pid == Process.myPid()) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Set the list of uids(from {@link Settings}) that is allowed to use restricted networks.
      *
@@ -1047,6 +1058,15 @@
      */
     public static void setUidsAllowedOnRestrictedNetworks(@NonNull Context context,
             @NonNull Set<Integer> uidList) {
+        final boolean calledFromSystem = isCallingFromSystem();
+        if (!calledFromSystem) {
+            // Enforce NETWORK_SETTINGS check if it's debug build. This is for MTS test only.
+            if (!Build.isDebuggable()) {
+                throw new SecurityException("Only system can set this setting.");
+            }
+            context.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS,
+                    "Requires NETWORK_SETTINGS permission");
+        }
         final String uids = getUidStringFromSet(uidList);
         Settings.Global.putString(context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS,
                 uids);
diff --git a/framework/src/android/net/InetAddressCompat.java b/framework/src/android/net/InetAddressCompat.java
deleted file mode 100644
index 6b7e75c..0000000
--- a/framework/src/android/net/InetAddressCompat.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2021 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;
-
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Compatibility utility for InetAddress core platform APIs.
- *
- * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
- * (only core_current). Most stable core platform APIs are included manually in the connectivity
- * build rules, but because InetAddress is also part of the base java SDK that is earlier on the
- * classpath, the extra core platform APIs are not seen.
- *
- * TODO (b/183097033): remove this utility as soon as core_current is part of module_current
- * @hide
- */
-public class InetAddressCompat {
-
-    /**
-     * @see InetAddress#clearDnsCache()
-     */
-    public static void clearDnsCache() {
-        try {
-            InetAddress.class.getMethod("clearDnsCache").invoke(null);
-        } catch (InvocationTargetException e) {
-            if (e.getCause() instanceof RuntimeException) {
-                throw (RuntimeException) e.getCause();
-            }
-            throw new IllegalStateException("Unknown InvocationTargetException", e.getCause());
-        } catch (IllegalAccessException | NoSuchMethodException e) {
-            Log.wtf(InetAddressCompat.class.getSimpleName(), "Error clearing DNS cache", e);
-        }
-    }
-
-    /**
-     * @see InetAddress#getAllByNameOnNet(String, int)
-     */
-    public static InetAddress[] getAllByNameOnNet(String host, int netId) throws
-            UnknownHostException {
-        return (InetAddress[]) callGetByNameMethod("getAllByNameOnNet", host, netId);
-    }
-
-    /**
-     * @see InetAddress#getByNameOnNet(String, int)
-     */
-    public static InetAddress getByNameOnNet(String host, int netId) throws
-            UnknownHostException {
-        return (InetAddress) callGetByNameMethod("getByNameOnNet", host, netId);
-    }
-
-    private static Object callGetByNameMethod(String method, String host, int netId)
-            throws UnknownHostException {
-        try {
-            return InetAddress.class.getMethod(method, String.class, int.class)
-                    .invoke(null, host, netId);
-        } catch (InvocationTargetException e) {
-            if (e.getCause() instanceof UnknownHostException) {
-                throw (UnknownHostException) e.getCause();
-            }
-            if (e.getCause() instanceof RuntimeException) {
-                throw (RuntimeException) e.getCause();
-            }
-            throw new IllegalStateException("Unknown InvocationTargetException", e.getCause());
-        } catch (IllegalAccessException | NoSuchMethodException e) {
-            Log.wtf(InetAddressCompat.class.getSimpleName(), "Error calling " + method, e);
-            throw new IllegalStateException("Error querying via " + method, e);
-        }
-    }
-}
diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java
index 1f49033..b3770ea 100644
--- a/framework/src/android/net/Network.java
+++ b/framework/src/android/net/Network.java
@@ -142,7 +142,7 @@
      * @throws UnknownHostException if the address lookup fails.
      */
     public InetAddress[] getAllByName(String host) throws UnknownHostException {
-        return InetAddressCompat.getAllByNameOnNet(host, getNetIdForResolv());
+        return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
     }
 
     /**
@@ -155,7 +155,7 @@
      *             if the address lookup fails.
      */
     public InetAddress getByName(String host) throws UnknownHostException {
-        return InetAddressCompat.getByNameOnNet(host, getNetIdForResolv());
+        return InetAddress.getByNameOnNet(host, getNetIdForResolv());
     }
 
     /**
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 7e62d28..9791cbf 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -182,7 +182,7 @@
     public void setTestAllowBadWifiUntil(long timeMs) {
         Log.d(TAG, "setTestAllowBadWifiUntil: " + mTestAllowBadWifiUntilMs);
         mTestAllowBadWifiUntilMs = timeMs;
-        updateAvoidBadWifi();
+        reevaluateInternal();
     }
 
     @VisibleForTesting
diff --git a/service/Android.bp b/service/Android.bp
index 841e189..848de12 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -57,9 +57,6 @@
         ":net-module-utils-srcs",
     ],
     libs: [
-        // TODO (b/183097033) remove once system_server_current includes core_current
-        "stable.core.platform.api.stubs",
-        "android_system_server_stubs_current",
         "framework-annotations-lib",
         "framework-connectivity.impl",
         "framework-tethering.stubs.module_lib",
@@ -112,3 +109,9 @@
         "com.android.tethering",
     ],
 }
+
+filegroup {
+    name: "connectivity-jarjar-rules",
+    srcs: ["jarjar-rules.txt"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 078a9eb..bf32ad5 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -107,4 +107,11 @@
     <string-array translatable="false" name="config_networkNotifySwitches">
     </string-array>
 
+    <!-- Whether to use an ongoing notification for signing in to captive portals, instead of a
+         notification that can be dismissed. -->
+    <bool name="config_ongoingSignInNotification">false</bool>
+
+    <!-- Whether to cancel network notifications automatically when tapped -->
+    <bool name="config_autoCancelNetworkNotifications">true</bool>
+
 </resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index f0f4ae8..6ac6a0e 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -30,7 +30,10 @@
             <item type="integer" name="config_networkWakeupPacketMask"/>
             <item type="integer" name="config_networkNotifySwitchType"/>
             <item type="array" name="config_networkNotifySwitches"/>
-
+            <item type="bool" name="config_ongoingSignInNotification"/>
+            <item type="bool" name="config_autoCancelNetworkNotifications"/>
+            <item type="drawable" name="stat_notify_wifi_in_range"/>
+            <item type="drawable" name="stat_notify_rssi_in_range"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 352d266..3c351ba 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -55,6 +55,7 @@
 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_SKIPPED;
 import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
@@ -541,9 +542,9 @@
     private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
 
     /**
-     * used to trigger revalidation of a network.
+     * used to handle reported network connectivity. May trigger revalidation of a network.
      */
-    private static final int EVENT_REVALIDATE_NETWORK = 36;
+    private static final int EVENT_REPORT_NETWORK_CONNECTIVITY = 36;
 
     // Handle changes in Private DNS settings.
     private static final int EVENT_PRIVATE_DNS_SETTINGS_CHANGED = 37;
@@ -3260,11 +3261,6 @@
                 }
                 case NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED: {
                     // TODO: prevent loops, e.g., if a network declares itself as underlying.
-                    if (!nai.supportsUnderlyingNetworks()) {
-                        Log.wtf(TAG, "Non-virtual networks cannot have underlying networks");
-                        break;
-                    }
-
                     final List<Network> underlying = (List<Network>) arg.second;
 
                     if (isLegacyLockdownNai(nai)
@@ -3541,8 +3537,14 @@
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(mNetId);
             if (nai == null) return;
 
+            // NetworkMonitor reports the network validation result as a bitmask while
+            // ConnectivityDiagnostics treats this value as an int. Convert the result to a single
+            // logical value for ConnectivityDiagnostics.
+            final int validationResult = networkMonitorValidationResultToConnDiagsValidationResult(
+                    p.result);
+
             final PersistableBundle extras = new PersistableBundle();
-            extras.putInt(KEY_NETWORK_VALIDATION_RESULT, p.result);
+            extras.putInt(KEY_NETWORK_VALIDATION_RESULT, validationResult);
             extras.putInt(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, p.probesSucceeded);
             extras.putInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK, p.probesAttempted);
 
@@ -3618,6 +3620,22 @@
         }
     }
 
+    /**
+     * Converts the given NetworkMonitor-specific validation result bitmask to a
+     * ConnectivityDiagnostics-specific validation result int.
+     */
+    private int networkMonitorValidationResultToConnDiagsValidationResult(int validationResult) {
+        if ((validationResult & NETWORK_VALIDATION_RESULT_SKIPPED) != 0) {
+            return ConnectivityReport.NETWORK_VALIDATION_RESULT_SKIPPED;
+        }
+        if ((validationResult & NETWORK_VALIDATION_RESULT_VALID) == 0) {
+            return ConnectivityReport.NETWORK_VALIDATION_RESULT_INVALID;
+        }
+        return (validationResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0
+                ? ConnectivityReport.NETWORK_VALIDATION_RESULT_PARTIALLY_VALID
+                : ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
+    }
+
     private void notifyDataStallSuspected(DataStallReportParcelable p, int netId) {
         log("Data stall detected with methods: " + p.detectionMethod);
 
@@ -4867,8 +4885,9 @@
                     mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
                     break;
                 }
-                case EVENT_REVALIDATE_NETWORK: {
-                    handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, toBool(msg.arg2));
+                case EVENT_REPORT_NETWORK_CONNECTIVITY: {
+                    handleReportNetworkConnectivity((NetworkAgentInfo) msg.obj, msg.arg1,
+                            toBool(msg.arg2));
                     break;
                 }
                 case EVENT_PRIVATE_DNS_SETTINGS_CHANGED:
@@ -5031,41 +5050,34 @@
         final int uid = mDeps.getCallingUid();
         final int connectivityInfo = encodeBool(hasConnectivity);
 
-        // Handle ConnectivityDiagnostics event before attempting to revalidate the network. This
-        // forces an ordering of ConnectivityDiagnostics events in the case where hasConnectivity
-        // does not match the known connectivity of the network - this causes NetworkMonitor to
-        // revalidate the network and generate a ConnectivityDiagnostics ConnectivityReport event.
         final NetworkAgentInfo nai;
         if (network == null) {
             nai = getDefaultNetwork();
         } else {
             nai = getNetworkAgentInfoForNetwork(network);
         }
-        if (nai != null) {
-            mConnectivityDiagnosticsHandler.sendMessage(
-                    mConnectivityDiagnosticsHandler.obtainMessage(
-                            ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
-                            connectivityInfo, 0, nai));
-        }
 
         mHandler.sendMessage(
-                mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+                mHandler.obtainMessage(
+                        EVENT_REPORT_NETWORK_CONNECTIVITY, uid, connectivityInfo, nai));
     }
 
     private void handleReportNetworkConnectivity(
-            Network network, int uid, boolean hasConnectivity) {
-        final NetworkAgentInfo nai;
-        if (network == null) {
-            nai = getDefaultNetwork();
-        } else {
-            nai = getNetworkAgentInfoForNetwork(network);
-        }
-        if (nai == null || nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTING ||
-            nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTED) {
+            @Nullable NetworkAgentInfo nai, int uid, boolean hasConnectivity) {
+        // TODO(b/192611346): remove NetworkInfo.State.DISCONNECTING as it's not used
+        if (nai == null
+                || nai != getNetworkAgentInfoForNetwork(nai.network)
+                || nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTING
+                || nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTED) {
             return;
         }
         // Revalidate if the app report does not match our current validated state.
         if (hasConnectivity == nai.lastValidated) {
+            mConnectivityDiagnosticsHandler.sendMessage(
+                    mConnectivityDiagnosticsHandler.obtainMessage(
+                            ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
+                            new ReportedNetworkConnectivityInfo(
+                                    hasConnectivity, false /* isNetworkRevalidating */, uid, nai)));
             return;
         }
         if (DBG) {
@@ -5081,6 +5093,16 @@
         if (isNetworkWithCapabilitiesBlocked(nc, uid, false)) {
             return;
         }
+
+        // Send CONNECTIVITY_REPORTED event before re-validating the Network to force an ordering of
+        // ConnDiags events. This ensures that #onNetworkConnectivityReported() will be called
+        // before #onConnectivityReportAvailable(), which is called once Network evaluation is
+        // completed.
+        mConnectivityDiagnosticsHandler.sendMessage(
+                mConnectivityDiagnosticsHandler.obtainMessage(
+                        ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
+                        new ReportedNetworkConnectivityInfo(
+                                hasConnectivity, true /* isNetworkRevalidating */, uid, nai)));
         nai.networkMonitor().forceReevaluation(uid);
     }
 
@@ -5277,12 +5299,12 @@
      *         information, e.g underlying ifaces.
      */
     private UnderlyingNetworkInfo createVpnInfo(NetworkAgentInfo nai) {
-        if (!nai.isVPN()) return null;
-
         Network[] underlyingNetworks = nai.declaredUnderlyingNetworks;
         // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
         // the underlyingNetworks list.
-        if (underlyingNetworks == null) {
+        // TODO: stop using propagateUnderlyingCapabilities here, for example, by always
+        // initializing NetworkAgentInfo#declaredUnderlyingNetworks to an empty array.
+        if (underlyingNetworks == null && nai.propagateUnderlyingCapabilities()) {
             final NetworkAgentInfo defaultNai = getDefaultNetworkForUid(
                     nai.networkCapabilities.getOwnerUid());
             if (defaultNai != null) {
@@ -5331,7 +5353,7 @@
     private boolean hasUnderlyingNetwork(NetworkAgentInfo nai, Network network) {
         // TODO: support more than one level of underlying networks, either via a fixed-depth search
         // (e.g., 2 levels of underlying networks), or via loop detection, or....
-        if (!nai.supportsUnderlyingNetworks()) return false;
+        if (!nai.propagateUnderlyingCapabilities()) return false;
         final Network[] underlying = underlyingNetworksOrDefault(
                 nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks);
         return CollectionUtils.contains(underlying, network);
@@ -6280,7 +6302,8 @@
                 callingAttributionTag);
         if (VDBG) log("pendingListenForNetwork for " + nri);
 
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+        mHandler.sendMessage(mHandler.obtainMessage(
+                    EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
     }
 
     /** Returns the next Network provider ID. */
@@ -7303,7 +7326,7 @@
             newNc.addCapability(NET_CAPABILITY_NOT_ROAMING);
         }
 
-        if (nai.supportsUnderlyingNetworks()) {
+        if (nai.propagateUnderlyingCapabilities()) {
             applyUnderlyingCapabilities(nai.declaredUnderlyingNetworks, nai.declaredCapabilities,
                     newNc);
         }
@@ -8445,7 +8468,7 @@
             networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
 
             if (!createNativeNetwork(networkAgent)) return;
-            if (networkAgent.supportsUnderlyingNetworks()) {
+            if (networkAgent.propagateUnderlyingCapabilities()) {
                 // Initialize the network's capabilities to their starting values according to the
                 // underlying networks. This ensures that the capabilities are correct before
                 // anything happens to the network.
@@ -9039,8 +9062,7 @@
          * the platform. This event will invoke {@link
          * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned
          * callbacks.
-         * obj = Network that was reported on
-         * arg1 = boolint for the quality reported
+         * obj = ReportedNetworkConnectivityInfo with info on reported Network connectivity.
          */
         private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5;
 
@@ -9078,7 +9100,7 @@
                     break;
                 }
                 case EVENT_NETWORK_CONNECTIVITY_REPORTED: {
-                    handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1));
+                    handleNetworkConnectivityReported((ReportedNetworkConnectivityInfo) msg.obj);
                     break;
                 }
                 default: {
@@ -9148,6 +9170,28 @@
         }
     }
 
+    /**
+     * Class used for sending info for a call to {@link #reportNetworkConnectivity()} to {@link
+     * ConnectivityDiagnosticsHandler}.
+     */
+    private static class ReportedNetworkConnectivityInfo {
+        public final boolean hasConnectivity;
+        public final boolean isNetworkRevalidating;
+        public final int reporterUid;
+        @NonNull public final NetworkAgentInfo nai;
+
+        private ReportedNetworkConnectivityInfo(
+                boolean hasConnectivity,
+                boolean isNetworkRevalidating,
+                int reporterUid,
+                @NonNull NetworkAgentInfo nai) {
+            this.hasConnectivity = hasConnectivity;
+            this.isNetworkRevalidating = isNetworkRevalidating;
+            this.reporterUid = reporterUid;
+            this.nai = nai;
+        }
+    }
+
     private void handleRegisterConnectivityDiagnosticsCallback(
             @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) {
         ensureRunningOnConnectivityServiceThread();
@@ -9255,13 +9299,14 @@
                         networkCapabilities,
                         extras);
         nai.setConnectivityReport(report);
+
         final List<IConnectivityDiagnosticsCallback> results =
-                getMatchingPermissionedCallbacks(nai);
+                getMatchingPermissionedCallbacks(nai, Process.INVALID_UID);
         for (final IConnectivityDiagnosticsCallback cb : results) {
             try {
                 cb.onConnectivityReportAvailable(report);
             } catch (RemoteException ex) {
-                loge("Error invoking onConnectivityReport", ex);
+                loge("Error invoking onConnectivityReportAvailable", ex);
             }
         }
     }
@@ -9280,7 +9325,7 @@
                         networkCapabilities,
                         extras);
         final List<IConnectivityDiagnosticsCallback> results =
-                getMatchingPermissionedCallbacks(nai);
+                getMatchingPermissionedCallbacks(nai, Process.INVALID_UID);
         for (final IConnectivityDiagnosticsCallback cb : results) {
             try {
                 cb.onDataStallSuspected(report);
@@ -9291,15 +9336,39 @@
     }
 
     private void handleNetworkConnectivityReported(
-            @NonNull NetworkAgentInfo nai, boolean connectivity) {
+            @NonNull ReportedNetworkConnectivityInfo reportedNetworkConnectivityInfo) {
+        final NetworkAgentInfo nai = reportedNetworkConnectivityInfo.nai;
+        final ConnectivityReport cachedReport = nai.getConnectivityReport();
+
+        // If the Network is being re-validated as a result of this call to
+        // reportNetworkConnectivity(), notify all permissioned callbacks. Otherwise, only notify
+        // permissioned callbacks registered by the reporter.
         final List<IConnectivityDiagnosticsCallback> results =
-                getMatchingPermissionedCallbacks(nai);
+                getMatchingPermissionedCallbacks(
+                        nai,
+                        reportedNetworkConnectivityInfo.isNetworkRevalidating
+                                ? Process.INVALID_UID
+                                : reportedNetworkConnectivityInfo.reporterUid);
+
         for (final IConnectivityDiagnosticsCallback cb : results) {
             try {
-                cb.onNetworkConnectivityReported(nai.network, connectivity);
+                cb.onNetworkConnectivityReported(
+                        nai.network, reportedNetworkConnectivityInfo.hasConnectivity);
             } catch (RemoteException ex) {
                 loge("Error invoking onNetworkConnectivityReported", ex);
             }
+
+            // If the Network isn't re-validating, also provide the cached report. If there is no
+            // cached report, the Network is still being validated and a report will be sent once
+            // validation is complete. Note that networks which never undergo validation will still
+            // have a cached ConnectivityReport with RESULT_SKIPPED.
+            if (!reportedNetworkConnectivityInfo.isNetworkRevalidating && cachedReport != null) {
+                try {
+                    cb.onConnectivityReportAvailable(cachedReport);
+                } catch (RemoteException ex) {
+                    loge("Error invoking onConnectivityReportAvailable", ex);
+                }
+            }
         }
     }
 
@@ -9312,20 +9381,38 @@
         return sanitized;
     }
 
+    /**
+     * Gets a list of ConnectivityDiagnostics callbacks that match the specified Network and uid.
+     *
+     * <p>If Process.INVALID_UID is specified, all matching callbacks will be returned.
+     */
     private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
-            @NonNull NetworkAgentInfo nai) {
+            @NonNull NetworkAgentInfo nai, int uid) {
         final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
         for (Entry<IBinder, ConnectivityDiagnosticsCallbackInfo> entry :
                 mConnectivityDiagnosticsCallbacks.entrySet()) {
             final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
             final NetworkRequestInfo nri = cbInfo.mRequestInfo;
+
             // Connectivity Diagnostics rejects multilayer requests at registration hence get(0).
-            if (nai.satisfies(nri.mRequests.get(0))) {
-                if (checkConnectivityDiagnosticsPermissions(
-                        nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
-                    results.add(entry.getValue().mCb);
-                }
+            if (!nai.satisfies(nri.mRequests.get(0))) {
+                continue;
             }
+
+            // UID for this callback must either be:
+            //  - INVALID_UID (which sends callbacks to all UIDs), or
+            //  - The callback's owner (the owner called reportNetworkConnectivity() and is being
+            //    notified as a result)
+            if (uid != Process.INVALID_UID && uid != nri.mUid) {
+                continue;
+            }
+
+            if (!checkConnectivityDiagnosticsPermissions(
+                    nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
+                continue;
+            }
+
+            results.add(entry.getValue().mCb);
         }
         return results;
     }
@@ -9354,7 +9441,7 @@
 
     private boolean ownsVpnRunningOverNetwork(int uid, Network network) {
         for (NetworkAgentInfo virtual : mNetworkAgentInfos) {
-            if (virtual.supportsUnderlyingNetworks()
+            if (virtual.propagateUnderlyingCapabilities()
                     && virtual.networkCapabilities.getOwnerUid() == uid
                     && CollectionUtils.contains(virtual.declaredUnderlyingNetworks, network)) {
                 return true;
@@ -9953,7 +10040,7 @@
         // - The request for the mobile network preferred.
         // - The request for the default network, for fallback.
         requests.add(createDefaultInternetRequestForTransport(
-                TRANSPORT_CELLULAR, NetworkRequest.Type.LISTEN));
+                TRANSPORT_CELLULAR, NetworkRequest.Type.REQUEST));
         requests.add(createDefaultInternetRequestForTransport(
                 TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
         final Set<UidRange> ranges = new ArraySet<>();
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index acf39f0..ee1538a 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -373,12 +373,10 @@
                     Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
                 }
             }
-            // Ignore the case when the network disconnects immediately after stop() has been
-            // called and the keepalive code is waiting for the response from the modem. This
-            // might happen when the caller listens for a lower-layer network disconnect
-            // callback and stop the keepalive at that time. But the stop() races with the
-            // stop() generated in ConnectivityService network disconnection code path.
-            if (mStartedState == STOPPING && reason == ERROR_INVALID_NETWORK) return;
+            // To prevent races from re-entrance of stop(), return if the state is already stopping.
+            // This might happen if multiple event sources stop keepalive in a short time. Such as
+            // network disconnect after user calls stop(), or tear down socket after binder died.
+            if (mStartedState == STOPPING) return;
 
             // Store the reason of stopping, and report it after the keepalive is fully stopped.
             if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 18becd4..bbf523a 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -157,8 +157,8 @@
     @NonNull public NetworkCapabilities networkCapabilities;
     @NonNull public final NetworkAgentConfig networkAgentConfig;
 
-    // Underlying networks declared by the agent. Only set if supportsUnderlyingNetworks is true.
-    // The networks in this list might be declared by a VPN app using setUnderlyingNetworks and are
+    // Underlying networks declared by the agent.
+    // The networks in this list might be declared by a VPN using setUnderlyingNetworks and are
     // not guaranteed to be current or correct, or even to exist.
     //
     // This array is read and iterated on multiple threads with no locking so its contents must
@@ -168,7 +168,7 @@
 
     // The capabilities originally announced by the NetworkAgent, regardless of any capabilities
     // that were added or removed due to this network's underlying networks.
-    // Only set if #supportsUnderlyingNetworks is true.
+    // Only set if #propagateUnderlyingCapabilities is true.
     public @Nullable NetworkCapabilities declaredCapabilities;
 
     // Indicates if netd has been told to create this Network. From this point on the appropriate
@@ -898,8 +898,11 @@
         return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
     }
 
-    /** Whether this network might have underlying networks. Currently only true for VPNs. */
-    public boolean supportsUnderlyingNetworks() {
+    /**
+     * Whether this network should propagate the capabilities from its underlying networks.
+     * Currently only true for VPNs.
+     */
+    public boolean propagateUnderlyingCapabilities() {
         return isVPN();
     }
 
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index b57ad5d..ae98d92 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -274,13 +274,17 @@
                 .setWhen(System.currentTimeMillis())
                 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
                 .setSmallIcon(icon)
-                .setAutoCancel(true)
+                .setAutoCancel(r.getBoolean(R.bool.config_autoCancelNetworkNotifications))
                 .setTicker(title)
                 .setColor(mContext.getColor(android.R.color.system_notification_accent_color))
                 .setContentTitle(title)
                 .setContentIntent(intent)
                 .setLocalOnly(true)
-                .setOnlyAlertOnce(true);
+                .setOnlyAlertOnce(true)
+                // TODO: consider having action buttons to disconnect on the sign-in notification
+                // especially if it is ongoing
+                .setOngoing(notifyType == NotificationType.SIGN_IN
+                        && r.getBoolean(R.bool.config_ongoingSignInNotification));
 
         if (notifyType == NotificationType.NETWORK_SWITCH) {
             builder.setStyle(new Notification.BigTextStyle().bigText(details));
diff --git a/service/src/com/android/server/connectivity/OsCompat.java b/service/src/com/android/server/connectivity/OsCompat.java
deleted file mode 100644
index 57e3dcd..0000000
--- a/service/src/com/android/server/connectivity/OsCompat.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.server.connectivity;
-
-import android.system.ErrnoException;
-import android.system.Os;
-
-import java.io.FileDescriptor;
-
-/**
- * Compatibility utility for android.system.Os core platform APIs.
- *
- * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
- * (only core_current). Most stable core platform APIs are included manually in the connectivity
- * build rules, but because Os is also part of the base java SDK that is earlier on the
- * classpath, the extra core platform APIs are not seen.
- *
- * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
- * @hide
- */
-public class OsCompat {
-    // This value should be correct on all architectures supported by Android, but hardcoding ioctl
-    // numbers should be avoided.
-    /**
-     * @see android.system.OsConstants#TIOCOUTQ
-     */
-    public static final int TIOCOUTQ = 0x5411;
-
-    /**
-     * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
-     */
-    public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
-            ErrnoException {
-        try {
-            return (int) Os.class.getMethod(
-                    "getsockoptInt", FileDescriptor.class, int.class, int.class)
-                    .invoke(null, fd, level, option);
-        } catch (ReflectiveOperationException e) {
-            if (e.getCause() instanceof ErrnoException) {
-                throw (ErrnoException) e.getCause();
-            }
-            throw new IllegalStateException("Error calling getsockoptInt", e);
-        }
-    }
-
-    /**
-     * @see android.system.Os#ioctlInt(FileDescriptor, int)
-     */
-    public static int ioctlInt(FileDescriptor fd, int cmd) throws
-            ErrnoException {
-        try {
-            return (int) Os.class.getMethod(
-                    "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
-        } catch (ReflectiveOperationException e) {
-            if (e.getCause() instanceof ErrnoException) {
-                throw (ErrnoException) e.getCause();
-            }
-            throw new IllegalStateException("Error calling ioctlInt", e);
-        }
-    }
-}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 99118ac..512d767 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -121,15 +121,23 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
-            final Uri packageData = intent.getData();
-            final String packageName =
-                    packageData != null ? packageData.getSchemeSpecificPart() : null;
 
             if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                final Uri packageData = intent.getData();
+                final String packageName =
+                        packageData != null ? packageData.getSchemeSpecificPart() : null;
                 onPackageAdded(packageName, uid);
             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                final Uri packageData = intent.getData();
+                final String packageName =
+                        packageData != null ? packageData.getSchemeSpecificPart() : null;
                 onPackageRemoved(packageName, uid);
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+                final String[] pkgList =
+                        intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                onExternalApplicationsAvailable(pkgList);
             } else {
                 Log.wtf(TAG, "received unexpected intent: " + action);
             }
@@ -194,6 +202,12 @@
                 mIntentReceiver, intentFilter, null /* broadcastPermission */,
                 null /* scheduler */);
 
+        final IntentFilter externalIntentFilter =
+                new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        userAllContext.registerReceiver(
+                mIntentReceiver, externalIntentFilter, null /* broadcastPermission */,
+                null /* scheduler */);
+
         // Register UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
         mDeps.registerContentObserver(
                 userAllContext,
@@ -812,6 +826,21 @@
         update(mUsers, removedUids, false /* add */);
     }
 
+    private synchronized void onExternalApplicationsAvailable(String[] pkgList) {
+        if (CollectionUtils.isEmpty(pkgList)) {
+            Log.e(TAG, "No available external application.");
+            return;
+        }
+
+        for (String app : pkgList) {
+            final PackageInfo info = getPackageInfo(app);
+            if (info == null || info.applicationInfo == null) continue;
+
+            final int appId = info.applicationInfo.uid;
+            onPackageAdded(app, appId); // Use onPackageAdded to add package one by one.
+        }
+    }
+
     /** Dump info to dumpsys */
     public void dump(IndentingPrintWriter pw) {
         pw.println("Interface filtering rules:");
diff --git a/service/src/com/android/server/connectivity/TcpKeepaliveController.java b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
index 73f3475..c480594 100644
--- a/service/src/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,8 +27,7 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IP_TOS;
 import static android.system.OsConstants.IP_TTL;
-
-import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
+import static android.system.OsConstants.TIOCOUTQ;
 
 import android.annotation.NonNull;
 import android.net.InvalidPacketException;
@@ -176,10 +175,10 @@
             }
             // Query write sequence number from SEND_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
-            tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Query read sequence number from RECV_QUEUE.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
-            tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+            tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
             // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
             Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
             // Finally, check if socket is still idle. TODO : this check needs to move to
@@ -199,9 +198,9 @@
             tcpDetails.rcvWndScale = trw.rcvWndScale;
             if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
                 // Query TOS.
-                tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+                tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
                 // Query TTL.
-                tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+                tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
             }
         } catch (ErrnoException e) {
             Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -306,7 +305,7 @@
 
     private static boolean isReceiveQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = OsCompat.ioctlInt(fd, SIOCINQ);
+        final int result = Os.ioctlInt(fd, SIOCINQ);
         if (result != 0) {
             Log.e(TAG, "Read queue has data");
             return false;
@@ -316,7 +315,7 @@
 
     private static boolean isSendQueueEmpty(FileDescriptor fd)
             throws ErrnoException {
-        final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
+        final int result = Os.ioctlInt(fd, SIOCOUTQ);
         if (result != 0) {
             Log.e(TAG, "Write queue has data");
             return false;
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 8be8ea1..e1fab09 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -43,6 +43,23 @@
     ],
 }
 
+// Connectivity coverage tests combines Tethering and Connectivity tests, each with their
+// respective jarjar rules applied.
+// Some tests may be duplicated (in particular static lib tests), as they need to be run under both
+// jarjared packages to cover both usages.
+android_library {
+    name: "ConnectivityCoverageTestsLib",
+    min_sdk_version: "30",
+    static_libs: [
+        "FrameworksNetTestsLib",
+        "NetdStaticLibTestsLib",
+        "NetworkStaticLibTestsLib",
+    ],
+    jarjar_rules: ":connectivity-jarjar-rules",
+    manifest: "AndroidManifest_coverage.xml",
+    visibility: ["//visibility:private"],
+}
+
 android_test {
     name: "ConnectivityCoverageTests",
     // Tethering started on SDK 30
@@ -62,13 +79,9 @@
         // (some tests would fail).
         // TODO: consider removing extended mockito usage in tests that use it, for performance
         "mockito-target-extended-minus-junit4",
-        "FrameworksNetTestsLib",
         "modules-utils-native-coverage-listener",
-        "NetdStaticLibTestsLib",
-        "NetworkStaticLibTestsLib",
-        "NetworkStackTestsLib",
-        "TetheringTestsLatestSdkLib",
-        "TetheringIntegrationTestsLatestSdkLib",
+        "ConnectivityCoverageTestsLib",
+        "TetheringCoverageTestsLib",
     ],
     jni_libs: [
         // For mockito extended
diff --git a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
index d4780d3..ab97f2d 100644
--- a/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
+++ b/tests/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -19,6 +19,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.net.module.util.NetworkCapabilitiesUtils.unpackBits;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.timeout;
@@ -31,8 +33,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.util.BitUtils;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,7 +49,7 @@
 @SmallTest
 public class IpConnectivityLogTest {
     private static final int FAKE_NET_ID = 100;
-    private static final int[] FAKE_TRANSPORT_TYPES = BitUtils.unpackBits(TRANSPORT_WIFI);
+    private static final int[] FAKE_TRANSPORT_TYPES = unpackBits(TRANSPORT_WIFI);
     private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
     private static final String FAKE_INTERFACE_NAME = "test";
     private static final IpReachabilityEvent FAKE_EV =
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
index ddc5fd4..ad7ec9e 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
@@ -23,6 +23,8 @@
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidRestrictedOnMeteredNetworks;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -79,6 +81,7 @@
         assertFalse(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
     }
 
+    @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE})
     @Test
     public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
         // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
@@ -103,6 +106,7 @@
         }
     }
 
+    @RequiredProperties({DATA_SAVER_MODE})
     @Test
     public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
         // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
@@ -182,6 +186,7 @@
         }
     }
 
+    @RequiredProperties({BATTERY_SAVER_MODE})
     @Test
     public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
         // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
@@ -209,6 +214,7 @@
         }
     }
 
+    @RequiredProperties({DATA_SAVER_MODE})
     @Test
     public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
         try {
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index a54fd64..86642ea 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -35,6 +35,7 @@
 import android.os.Build;
 import android.os.connectivity.CellularBatteryStats;
 import android.os.connectivity.WifiBatteryStats;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -80,6 +81,7 @@
     }
 
     @Test
+    @AppModeFull(reason = "Cannot get CHANGE_NETWORK_STATE to request wifi/cell in instant mode")
     @SkipPresubmit(reason = "Virtual hardware does not support wifi battery stats")
     public void testReportNetworkInterfaceForTransports() throws Exception {
         try {
@@ -132,6 +134,7 @@
     }
 
     @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
     public void testReportNetworkInterfaceForTransports_throwsSecurityException()
             throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index ccbdbd3..a40c92d 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -22,6 +22,7 @@
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_SKIPPED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS;
@@ -78,6 +79,7 @@
 
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.ArrayTrackRecord;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -292,7 +294,7 @@
         final String interfaceName =
                 mConnectivityManager.getLinkProperties(network).getInterfaceName();
         connDiagsCallback.expectOnConnectivityReportAvailable(
-                network, interfaceName, TRANSPORT_CELLULAR);
+                network, interfaceName, TRANSPORT_CELLULAR, NETWORK_VALIDATION_RESULT_VALID);
         connDiagsCallback.assertNoCallback();
     }
 
@@ -423,9 +425,10 @@
 
         cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity);
 
-        // if hasConnectivity does not match the network's known connectivity, it will be
-        // revalidated which will trigger another onConnectivityReportAvailable callback.
-        if (!hasConnectivity) {
+        // All calls to #onNetworkConnectivityReported are expected to be accompanied by a call to
+        // #onConnectivityReportAvailable for S+ (for R, ConnectivityReports were only sent when the
+        // Network was re-validated - when reported connectivity != known connectivity).
+        if (SdkLevel.isAtLeastS() || !hasConnectivity) {
             cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
         }
 
@@ -479,11 +482,19 @@
 
         public void expectOnConnectivityReportAvailable(
                 @NonNull Network network, @NonNull String interfaceName) {
-            expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST);
+            // Test Networks both do not require validation and are not tested for validation. This
+            // results in the validation result being reported as SKIPPED for S+ (for R, the
+            // platform marked these Networks as VALID).
+            final int expectedNetworkValidationResult =
+                    SdkLevel.isAtLeastS()
+                            ? NETWORK_VALIDATION_RESULT_SKIPPED
+                            : NETWORK_VALIDATION_RESULT_VALID;
+            expectOnConnectivityReportAvailable(
+                    network, interfaceName, TRANSPORT_TEST, expectedNetworkValidationResult);
         }
 
-        public void expectOnConnectivityReportAvailable(
-                @NonNull Network network, @NonNull String interfaceName, int transportType) {
+        public void expectOnConnectivityReportAvailable(@NonNull Network network,
+                @NonNull String interfaceName, int transportType, int expectedValidationResult) {
             final ConnectivityReport result =
                     (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
             assertEquals(network, result.getNetwork());
@@ -496,9 +507,9 @@
 
             final PersistableBundle extras = result.getAdditionalInfo();
             assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT));
-            final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
-            assertEquals("Network validation result is not 'valid'",
-                    NETWORK_VALIDATION_RESULT_VALID, validationResult);
+            final int actualValidationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
+            assertEquals("Network validation result is incorrect",
+                    expectedValidationResult, actualValidationResult);
 
             assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK));
             final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a19c7a6..527897e 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -72,6 +72,7 @@
 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -499,6 +500,7 @@
     @Test
     public void testGetAllNetworkStateSnapshots()
             throws InterruptedException {
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
         // Make sure cell is active to retrieve IMSI for verification in later step.
         final Network cellNetwork = mCtsNetUtils.connectToCell();
         final String subscriberId = getSubscriberIdForCellNetwork(cellNetwork);
@@ -936,8 +938,8 @@
             // noticeably flaky.
             Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
 
-            // TODO: BUG (b/189868426): this should also apply to listens
-            if (!useListen) {
+            // For R- frameworks, listens will receive duplicated callbacks. See b/189868426.
+            if (isAtLeastS() || !useListen) {
                 assertEquals("PendingIntent should only be received once", 1, receivedCount.get());
             }
         } finally {
@@ -952,8 +954,8 @@
             boolean useListen) {
         assertArrayEquals(filed.networkCapabilities.getCapabilities(),
                 broadcasted.networkCapabilities.getCapabilities());
-        // TODO: BUG (b/189868426): this should also apply to listens
-        if (useListen) return;
+        // For R- frameworks, listens will receive duplicated callbacks. See b/189868426.
+        if (!isAtLeastS() && useListen) return;
         assertArrayEquals(filed.networkCapabilities.getTransportTypes(),
                 broadcasted.networkCapabilities.getTransportTypes());
     }
@@ -1077,7 +1079,7 @@
         final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
                 Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
         if (!m.find()) {
-            fail("Unexpected format from cmd netpolicy");
+            fail("Unexpected format from cmd netpolicy, policyString = " + policyString);
         }
         return m.group(1);
     }
@@ -2874,6 +2876,10 @@
     public void testUidsAllowedOnRestrictedNetworks() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
 
+        // TODO (b/175199465): figure out a reasonable permission check for
+        //  setUidsAllowedOnRestrictedNetworks that allows tests but not system-external callers.
+        assumeTrue(Build.isDebuggable());
+
         final int uid = mPackageManager.getPackageUid(mContext.getPackageName(), 0 /* flag */);
         final Set<Integer> originalUidsAllowedOnRestrictedNetworks =
                 ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(mContext);
@@ -2881,8 +2887,9 @@
         // because it has been just installed to device. In case the uid is existed in setting
         // mistakenly, try to remove the uid and set correct uids to setting.
         originalUidsAllowedOnRestrictedNetworks.remove(uid);
-        ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
-                originalUidsAllowedOnRestrictedNetworks);
+        runWithShellPermissionIdentity(() ->
+                ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
+                        mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
 
         final Handler h = new Handler(Looper.getMainLooper());
         final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
@@ -2929,8 +2936,9 @@
             final Set<Integer> newUidsAllowedOnRestrictedNetworks =
                     new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
             newUidsAllowedOnRestrictedNetworks.add(uid);
-            ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
-                    newUidsAllowedOnRestrictedNetworks);
+            runWithShellPermissionIdentity(() ->
+                    ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
+                            mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
             // Wait a while for sending allowed uids on the restricted network to netd.
             // TODD: Have a significant signal to know the uids has been send to netd.
             assertBindSocketToNetworkSuccess(network);
@@ -2939,8 +2947,9 @@
             agent.unregister();
 
             // Restore setting.
-            ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
-                    originalUidsAllowedOnRestrictedNetworks);
+            runWithShellPermissionIdentity(() ->
+                    ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
+                            mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
         }
     }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index ccc9416..7c380e3 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -101,6 +101,7 @@
 import com.android.testutils.TestableNetworkCallback
 import org.junit.After
 import org.junit.Assert.assertArrayEquals
+import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -1034,6 +1035,9 @@
 
     @Test
     fun testQosCallbackRegisterWithUnregister() {
+        // Instant apps can't bind sockets to localhost
+        // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
+        assumeFalse(realContext.packageManager.isInstantApp())
         val (agent, socket) = setupForQosCallbackTesting()
 
         val qosCallback = TestableQosCallback()
@@ -1060,6 +1064,9 @@
 
     @Test
     fun testQosCallbackOnQosSession() {
+        // Instant apps can't bind sockets to localhost
+        // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
+        assumeFalse(realContext.packageManager.isInstantApp())
         val (agent, socket) = setupForQosCallbackTesting()
         val qosCallback = TestableQosCallback()
         Executors.newSingleThreadExecutor().let { executor ->
@@ -1104,6 +1111,9 @@
 
     @Test
     fun testQosCallbackOnError() {
+        // Instant apps can't bind sockets to localhost
+        // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
+        assumeFalse(realContext.packageManager.isInstantApp())
         val (agent, socket) = setupForQosCallbackTesting()
         val qosCallback = TestableQosCallback()
         Executors.newSingleThreadExecutor().let { executor ->
@@ -1142,6 +1152,9 @@
 
     @Test
     fun testQosCallbackIdsAreMappedCorrectly() {
+        // Instant apps can't bind sockets to localhost
+        // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
+        assumeFalse(realContext.packageManager.isInstantApp())
         val (agent, socket) = setupForQosCallbackTesting()
         val qosCallback1 = TestableQosCallback()
         val qosCallback2 = TestableQosCallback()
@@ -1182,6 +1195,9 @@
 
     @Test
     fun testQosCallbackWhenNetworkReleased() {
+        // Instant apps can't bind sockets to localhost
+        // TODO: use @AppModeFull when supported by DevSdkIgnoreRunner
+        assumeFalse(realContext.packageManager.isInstantApp())
         val (agent, socket) = setupForQosCallbackTesting()
         Executors.newSingleThreadExecutor().let { executor ->
             try {
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
index 7d5e9ff..a20f1cc 100644
--- a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -35,6 +35,7 @@
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
 
@@ -145,6 +146,7 @@
         }
     }
 
+    @AppModeFull(reason = "Instant apps can't bind sockets to localhost for a test proxy server")
     @Test
     public void testSetCurrentProxyScriptUrl() throws Exception {
         // Register a PacProxyInstalledListener
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 0a5e506..bd1b74a 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -361,6 +361,7 @@
     @Test
     public void testRequestLatestEntitlementResult() throws Exception {
         assumeTrue(mTM.isTetheringSupported());
+        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
         // Verify that requestLatestTetheringEntitlementResult() can get entitlement
         // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via listener.
         assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index 17db179..95ea401 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -55,6 +55,7 @@
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkCallback;
 
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -255,6 +256,15 @@
         }
     }
 
+    public void setUnderlyingNetworks(List<Network> underlyingNetworks) {
+        mNetworkAgent.setUnderlyingNetworks(underlyingNetworks);
+    }
+
+    public void setOwnerUid(int uid) {
+        mNetworkCapabilities.setOwnerUid(uid);
+        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+    }
+
     public void connect() {
         if (!mConnected.compareAndSet(false /* expect */, true /* update */)) {
             // compareAndSet returns false when the value couldn't be updated because it did not
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 6bd2bd5..08a3007 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -36,11 +36,14 @@
 import android.net.NetworkStats.Entry;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.os.Build;
 import android.os.RemoteException;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,8 +52,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsManagerTest {
     private static final String TEST_SUBSCRIBER_ID = "subid";
 
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index b8cd3f6..e7873af 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -68,7 +68,9 @@
 import android.os.Process;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -77,8 +79,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
 public class ConnectivityManagerTest {
 
     @Mock Context mCtx;
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 0707ef3..56e5c62 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -23,14 +23,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.os.Build;
 import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.net.VpnProfile;
 import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
 import com.android.net.module.util.ProxyUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +53,8 @@
 
 /** Unit tests for {@link Ikev2VpnProfile.Builder}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class Ikev2VpnProfileTest {
     private static final String SERVER_ADDR_STRING = "1.2.3.4";
     private static final String IDENTITY_STRING = "Identity";
diff --git a/tests/unit/java/android/net/IpMemoryStoreTest.java b/tests/unit/java/android/net/IpMemoryStoreTest.java
index 0b13800..0b82759 100644
--- a/tests/unit/java/android/net/IpMemoryStoreTest.java
+++ b/tests/unit/java/android/net/IpMemoryStoreTest.java
@@ -36,10 +36,13 @@
 import android.net.ipmemorystore.NetworkAttributesParcelable;
 import android.net.ipmemorystore.Status;
 import android.net.networkstack.ModuleNetworkStackClient;
+import android.os.Build;
 import android.os.RemoteException;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,8 +56,9 @@
 import java.net.UnknownHostException;
 import java.util.Arrays;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpMemoryStoreTest {
     private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
     private static final String TEST_CLIENT_ID = "testClientId";
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
index 3a8d600..cac8c2d 100644
--- a/tests/unit/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -29,9 +29,10 @@
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.CollectionUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +46,8 @@
 
 /** Unit tests for {@link IpSecAlgorithm}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecAlgorithmTest {
     private static final byte[] KEY_MATERIAL;
 
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
index 25e225e..b87cb48 100644
--- a/tests/unit/java/android/net/IpSecConfigTest.java
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -23,15 +23,20 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
 
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 /** Unit tests for {@link IpSecConfig}. */
 @SmallTest
-@RunWith(JUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecConfigTest {
 
     @Test
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
index 730e2d5..cda8eb7 100644
--- a/tests/unit/java/android/net/IpSecManagerTest.java
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -31,13 +31,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.os.Build;
 import android.system.Os;
 import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.IpSecService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,7 +51,8 @@
 
 /** Unit tests for {@link IpSecManager}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecManagerTest {
 
     private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index 424f23d..81375f1 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -19,15 +19,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
 
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 /** Unit tests for {@link IpSecTransform}. */
 @SmallTest
-@RunWith(JUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecTransformTest {
 
     @Test
diff --git a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
index bc6dbf2..ed4f61d 100644
--- a/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
+++ b/tests/unit/java/android/net/KeepalivePacketDataUtilTest.java
@@ -24,18 +24,22 @@
 import static org.junit.Assert.fail;
 
 import android.net.util.KeepalivePacketDataUtil;
+import android.os.Build;
 import android.util.Log;
 
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
 
-@RunWith(JUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public final class KeepalivePacketDataUtilTest {
     private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 1};
     private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 5};
diff --git a/tests/unit/java/android/net/MacAddressTest.java b/tests/unit/java/android/net/MacAddressTest.java
index 6de31f6..ae7deaa 100644
--- a/tests/unit/java/android/net/MacAddressTest.java
+++ b/tests/unit/java/android/net/MacAddressTest.java
@@ -22,10 +22,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.net.module.util.MacAddressUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,7 +38,8 @@
 import java.util.Random;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class MacAddressTest {
 
     static class AddrTypeTestCase {
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index eb2b85c..f963593 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -20,12 +20,15 @@
 import android.net.NetworkIdentity.OEM_PAID
 import android.net.NetworkIdentity.OEM_PRIVATE
 import android.net.NetworkIdentity.getOemBitfield
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import kotlin.test.assertEquals
 
-@RunWith(JUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class NetworkIdentityTest {
     @Test
     fun testGetOemBitfield() {
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index 13558cd..c5f8c00 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -38,13 +38,15 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.os.Build;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Test;
@@ -56,8 +58,9 @@
 import java.io.DataOutputStream;
 import java.util.Random;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsHistoryTest {
     private static final String TAG = "NetworkStatsHistoryTest";
 
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index 7e0cc67..c971da1 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -39,11 +39,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Build;
 import android.os.Process;
 import android.util.ArrayMap;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import com.google.android.collect.Sets;
 
@@ -53,8 +56,9 @@
 import java.util.Arrays;
 import java.util.HashSet;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsTest {
 
     private static final String TEST_IFACE = "test0";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index cb39a0c..49c7271 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -19,10 +19,10 @@
 import android.content.Context
 import android.net.ConnectivityManager.TYPE_MOBILE
 import android.net.ConnectivityManager.TYPE_WIFI
-import android.net.NetworkIdentity.SUBTYPE_COMBINED
 import android.net.NetworkIdentity.OEM_NONE
 import android.net.NetworkIdentity.OEM_PAID
 import android.net.NetworkIdentity.OEM_PRIVATE
+import android.net.NetworkIdentity.SUBTYPE_COMBINED
 import android.net.NetworkIdentity.buildNetworkIdentity
 import android.net.NetworkStats.DEFAULT_NETWORK_ALL
 import android.net.NetworkStats.METERED_ALL
@@ -31,23 +31,25 @@
 import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
 import android.net.NetworkTemplate.MATCH_WIFI
 import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
-import android.net.NetworkTemplate.WIFI_NETWORKID_ALL
 import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA
 import android.net.NetworkTemplate.NETWORK_TYPE_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_NO
 import android.net.NetworkTemplate.OEM_MANAGED_YES
 import android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT
-import android.net.NetworkTemplate.buildTemplateWifi
-import android.net.NetworkTemplate.buildTemplateWifiWildcard
+import android.net.NetworkTemplate.WIFI_NETWORKID_ALL
 import android.net.NetworkTemplate.buildTemplateCarrierMetered
 import android.net.NetworkTemplate.buildTemplateMobileWithRatType
+import android.net.NetworkTemplate.buildTemplateWifi
+import android.net.NetworkTemplate.buildTemplateWifiWildcard
+import android.os.Build
 import android.telephony.TelephonyManager
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.assertParcelSane
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 import kotlin.test.assertEquals
@@ -60,7 +62,8 @@
 private const val TEST_SSID1 = "ssid1"
 private const val TEST_SSID2 = "ssid2"
 
-@RunWith(JUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class NetworkTemplateTest {
     private val mockContext = mock(Context::class.java)
 
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
index 7748288..a28245d 100644
--- a/tests/unit/java/android/net/NetworkUtilsTest.java
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -18,7 +18,12 @@
 
 import static junit.framework.Assert.assertEquals;
 
-import androidx.test.runner.AndroidJUnit4;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,8 +31,9 @@
 import java.math.BigInteger;
 import java.util.TreeSet;
 
-@RunWith(AndroidJUnit4.class)
-@androidx.test.filters.SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkUtilsTest {
     @Test
     public void testRoutedIPv4AddressCount() {
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 40f8f1b..91f2cdd 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -19,7 +19,12 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import androidx.test.runner.AndroidJUnit4;
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -27,8 +32,9 @@
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 
-@RunWith(AndroidJUnit4.class)
-@androidx.test.filters.SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class QosSocketFilterTest {
 
     @Test
diff --git a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
index 6714bb1..ead964e 100644
--- a/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
+++ b/tests/unit/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -23,16 +23,20 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.wifi.WifiNetworkSpecifier;
+import android.os.Build;
 import android.telephony.SubscriptionManager;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.testutils.DevSdkIgnoreRule;
+
 import org.junit.Test;
 
 /**
  * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
  */
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class TelephonyNetworkSpecifierTest {
     private static final int TEST_SUBID = 5;
     private static final String TEST_SSID = "Test123";
diff --git a/tests/unit/java/android/net/VpnManagerTest.java b/tests/unit/java/android/net/VpnManagerTest.java
index 3135062..532081a 100644
--- a/tests/unit/java/android/net/VpnManagerTest.java
+++ b/tests/unit/java/android/net/VpnManagerTest.java
@@ -27,14 +27,16 @@
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Build;
 import android.test.mock.MockContext;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.MessageUtils;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -42,7 +44,8 @@
 
 /** Unit tests for {@link VpnManager}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class VpnManagerTest {
     private static final String PKG_NAME = "fooPackage";
 
diff --git a/tests/unit/java/android/net/VpnTransportInfoTest.java b/tests/unit/java/android/net/VpnTransportInfoTest.java
index ccaa5cf..b4c7ac4 100644
--- a/tests/unit/java/android/net/VpnTransportInfoTest.java
+++ b/tests/unit/java/android/net/VpnTransportInfoTest.java
@@ -24,14 +24,19 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class VpnTransportInfoTest {
 
     @Test
diff --git a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
index 603c875..5d0b783 100644
--- a/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/unit/java/android/net/ipmemorystore/ParcelableTests.java
@@ -21,11 +21,14 @@
 
 import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
 import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirkParcelable;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,8 +39,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class ParcelableTests {
     @Test
     public void testNetworkAttributesParceling() throws Exception {
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 370179c..31c8927 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -35,9 +36,10 @@
 import android.os.Messenger;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.AsyncChannel;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
@@ -47,8 +49,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NsdManagerTest {
 
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index 94dfc75..ca8cf07 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -21,12 +21,15 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.StrictMode;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,8 +39,9 @@
 import java.util.Arrays;
 import java.util.Map;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NsdServiceInfoTest {
 
     public final static InetAddress LOCALHOST;
diff --git a/tests/unit/java/android/net/util/DnsUtilsTest.java b/tests/unit/java/android/net/util/DnsUtilsTest.java
index b626db8..660d516 100644
--- a/tests/unit/java/android/net/util/DnsUtilsTest.java
+++ b/tests/unit/java/android/net/util/DnsUtilsTest.java
@@ -25,9 +25,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.InetAddresses;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,8 +40,9 @@
 import java.util.Collections;
 import java.util.List;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class DnsUtilsTest {
     private InetAddress stringToAddress(@NonNull String addr) {
         return InetAddresses.parseNumericAddress(addr);
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index b62bdbc..40f39a4 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -25,15 +25,17 @@
 import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.os.Build
 import androidx.test.filters.SmallTest
 import com.android.internal.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
 import org.junit.Assert.assertArrayEquals
 import org.junit.Assert.assertEquals
 import org.junit.Assert.fail
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.any
 import org.mockito.Mockito.doReturn
@@ -45,8 +47,9 @@
  * Build, install and run with:
  * atest android.net.util.KeepaliveUtilsTest
  */
-@RunWith(JUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class KeepaliveUtilsTest {
 
     // Prepare mocked context with given resource strings.
diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
index 78c8fa4..576b8d3 100644
--- a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
@@ -25,15 +25,17 @@
 import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
 import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
 import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
+import android.os.Build
 import android.provider.Settings
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import android.test.mock.MockContentResolver
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
 import com.android.connectivity.resources.R
 import com.android.internal.util.test.FakeSettingsProvider
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -57,8 +59,9 @@
  * Build, install and run with:
  * atest android.net.util.MultinetworkPolicyTrackerTest
  */
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class MultinetworkPolicyTrackerTest {
     private val resources = mock(Resources::class.java).also {
         doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
diff --git a/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
index 3cfecd5..51388d4 100644
--- a/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
+++ b/tests/unit/java/com/android/internal/net/NetworkUtilsInternalTest.java
@@ -27,18 +27,23 @@
 
 import static org.junit.Assert.fail;
 
+import android.os.Build;
 import android.system.ErrnoException;
 import android.system.Os;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import libcore.io.IoUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@RunWith(AndroidJUnit4.class)
-@androidx.test.filters.SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkUtilsInternalTest {
 
     private static void expectSocketSuccess(String msg, int domain, int type) {
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index cb0f071..a945a1f 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -25,12 +25,15 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.IpSecAlgorithm;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -38,7 +41,8 @@
 
 /** Unit tests for {@link VpnProfile}. */
 @SmallTest
-@RunWith(JUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class VpnProfileTest {
     private static final String DUMMY_PROFILE_KEY = "Test";
 
diff --git a/tests/unit/java/com/android/internal/util/BitUtilsTest.java b/tests/unit/java/com/android/internal/util/BitUtilsTest.java
index d2fbdce..aab1268 100644
--- a/tests/unit/java/com/android/internal/util/BitUtilsTest.java
+++ b/tests/unit/java/com/android/internal/util/BitUtilsTest.java
@@ -30,8 +30,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,7 +45,8 @@
 import java.util.Random;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class BitUtilsTest {
 
     @Test
diff --git a/tests/unit/java/com/android/internal/util/RingBufferTest.java b/tests/unit/java/com/android/internal/util/RingBufferTest.java
index d06095a..13cf840 100644
--- a/tests/unit/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/unit/java/com/android/internal/util/RingBufferTest.java
@@ -20,14 +20,19 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
+import android.os.Build;
+
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class RingBufferTest {
 
     @Test
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6c484cc..cec3882 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -301,7 +301,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.connectivity.resources.R;
 import com.android.internal.app.IBatteryStats;
@@ -325,6 +324,8 @@
 import com.android.server.connectivity.Vpn;
 import com.android.server.connectivity.VpnProfileStore;
 import com.android.server.net.NetworkPinner;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.ExceptionUtils;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
@@ -388,8 +389,9 @@
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.ConnectivityServiceTest
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class ConnectivityServiceTest {
     private static final String TAG = "ConnectivityServiceTest";
 
@@ -5448,6 +5450,8 @@
             // the follow-up network disconnection will be processed first.
             mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
             ka.stop();
+            // Call stop() twice shouldn't result in crash, b/182586681.
+            ka.stop();
 
             // Make sure the stop has been processed. Wait for executor idle is needed to prevent
             // flaky since the actual stop call to the service is delegated to executor thread.
@@ -5745,37 +5749,59 @@
     @Test
     public void testNetworkCallbackMaximum() throws Exception {
         final int MAX_REQUESTS = 100;
-        final int CALLBACKS = 89;
-        final int INTENTS = 11;
+        final int CALLBACKS = 87;
+        final int DIFF_INTENTS = 10;
+        final int SAME_INTENTS = 10;
         final int SYSTEM_ONLY_MAX_REQUESTS = 250;
-        assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
+        // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS +
+        // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
+        assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1);
 
         NetworkRequest networkRequest = new NetworkRequest.Builder().build();
         ArrayList<Object> registered = new ArrayList<>();
 
-        int j = 0;
-        while (j++ < CALLBACKS / 2) {
-            NetworkCallback cb = new NetworkCallback();
-            mCm.requestNetwork(networkRequest, cb);
+        for (int j = 0; j < CALLBACKS; j++) {
+            final NetworkCallback cb = new NetworkCallback();
+            if (j < CALLBACKS / 2) {
+                mCm.requestNetwork(networkRequest, cb);
+            } else {
+                mCm.registerNetworkCallback(networkRequest, cb);
+            }
             registered.add(cb);
         }
-        while (j++ < CALLBACKS) {
-            NetworkCallback cb = new NetworkCallback();
-            mCm.registerNetworkCallback(networkRequest, cb);
-            registered.add(cb);
+
+        // Since ConnectivityService will de-duplicate the request with the same intent,
+        // register multiple times does not really increase multiple requests.
+        final PendingIntent same_pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                new Intent("same"), FLAG_IMMUTABLE);
+        for (int j = 0; j < SAME_INTENTS; j++) {
+            mCm.registerNetworkCallback(networkRequest, same_pi);
+            // Wait for the requests with the same intent to be de-duplicated. Because
+            // ConnectivityService side incrementCountOrThrow in binder, decrementCount in handler
+            // thread, waitForIdle is needed to ensure decrementCount being invoked for same intent
+            // requests before doing further tests.
+            waitForIdle();
         }
-        j = 0;
-        while (j++ < INTENTS / 2) {
-            final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
-                    new Intent("a" + j), FLAG_IMMUTABLE);
-            mCm.requestNetwork(networkRequest, pi);
-            registered.add(pi);
+        for (int j = 0; j < SAME_INTENTS; j++) {
+            mCm.requestNetwork(networkRequest, same_pi);
+            // Wait for the requests with the same intent to be de-duplicated.
+            // Refer to the reason above.
+            waitForIdle();
         }
-        while (j++ < INTENTS) {
-            final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
-                    new Intent("b" + j), FLAG_IMMUTABLE);
-            mCm.registerNetworkCallback(networkRequest, pi);
-            registered.add(pi);
+        registered.add(same_pi);
+
+        for (int j = 0; j < DIFF_INTENTS; j++) {
+            if (j < DIFF_INTENTS / 2) {
+                final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                        new Intent("a" + j), FLAG_IMMUTABLE);
+                mCm.requestNetwork(networkRequest, pi);
+                registered.add(pi);
+            } else {
+                final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                        new Intent("b" + j), FLAG_IMMUTABLE);
+                mCm.registerNetworkCallback(networkRequest, pi);
+                registered.add(pi);
+            }
         }
 
         // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
@@ -5825,10 +5851,10 @@
 
         for (Object o : registered) {
             if (o instanceof NetworkCallback) {
-                mCm.unregisterNetworkCallback((NetworkCallback)o);
+                mCm.unregisterNetworkCallback((NetworkCallback) o);
             }
             if (o instanceof PendingIntent) {
-                mCm.unregisterNetworkCallback((PendingIntent)o);
+                mCm.unregisterNetworkCallback((PendingIntent) o);
             }
         }
         waitForIdle();
@@ -6003,7 +6029,7 @@
         verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(networksCaptor.capture(),
                 any(List.class), eq(defaultIface), vpnInfosCaptor.capture());
 
-        assertSameElements(networksCaptor.getValue(), networks);
+        assertSameElements(networks, networksCaptor.getValue());
 
         List<UnderlyingNetworkInfo> infos = vpnInfosCaptor.getValue();
         if (vpnUid != null) {
@@ -6208,6 +6234,77 @@
     }
 
     @Test
+    public void testNonVpnUnderlyingNetworks() throws Exception {
+        // Ensure wifi and cellular are not torn down.
+        for (int transport : new int[]{TRANSPORT_CELLULAR, TRANSPORT_WIFI}) {
+            final NetworkRequest request = new NetworkRequest.Builder()
+                    .addTransportType(transport)
+                    .removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                    .build();
+            mCm.requestNetwork(request, new NetworkCallback());
+        }
+
+        // Connect a VCN-managed wifi network.
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiNetworkAgent.connect(true /* validated */);
+
+        final List<Network> none = List.of();
+        expectNotifyNetworkStatus(none, null);  // Wifi is not the default network
+
+        // Create a virtual network based on the wifi network.
+        final int ownerUid = 10042;
+        NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .setOwnerUid(ownerUid)
+                .setAdministratorUids(new int[]{ownerUid})
+                .build();
+        final String vcnIface = "ipsec42";
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(vcnIface);
+        final TestNetworkAgentWrapper vcn = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, lp, nc);
+        vcn.setUnderlyingNetworks(List.of(mWiFiNetworkAgent.getNetwork()));
+        vcn.connect(false /* validated */);
+
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(callback);
+        callback.expectAvailableCallbacksUnvalidated(vcn);
+
+        // The underlying wifi network's capabilities are not propagated to the virtual network,
+        // but NetworkStatsService is informed of the underlying interface.
+        nc = mCm.getNetworkCapabilities(vcn.getNetwork());
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+        final List<Network> onlyVcn = List.of(vcn.getNetwork());
+        expectNotifyNetworkStatus(onlyVcn, vcnIface, ownerUid, vcnIface, List.of(WIFI_IFNAME));
+
+        // Add NOT_METERED to the underlying network, check that it is not propagated.
+        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        callback.assertNoCallback();
+        nc = mCm.getNetworkCapabilities(vcn.getNetwork());
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
+
+        // Switch underlying networks.
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName(MOBILE_IFNAME);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        mCellNetworkAgent.connect(false /* validated */);
+        vcn.setUnderlyingNetworks(List.of(mCellNetworkAgent.getNetwork()));
+
+        // The underlying capability changes do not propagate to the virtual network, but
+        // NetworkStatsService is informed of the new underlying interface.
+        callback.assertNoCallback();
+        nc = mCm.getNetworkCapabilities(vcn.getNetwork());
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+        expectNotifyNetworkStatus(onlyVcn, vcnIface, ownerUid, vcnIface, List.of(MOBILE_IFNAME));
+    }
+
+    @Test
     public void testBasicDnsConfigurationPushed() throws Exception {
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
 
@@ -10195,6 +10292,9 @@
     public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception {
         setUpConnectivityDiagnosticsCallback();
 
+        // reset to ignore callbacks from setup
+        reset(mConnectivityDiagnosticsCallback);
+
         final Network n = mCellNetworkAgent.getNetwork();
         final boolean hasConnectivity = true;
         mService.reportNetworkConnectivity(n, hasConnectivity);
@@ -10205,6 +10305,8 @@
         // Verify onNetworkConnectivityReported fired
         verify(mConnectivityDiagnosticsCallback)
                 .onNetworkConnectivityReported(eq(n), eq(hasConnectivity));
+        verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable(
+                argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities())));
 
         final boolean noConnectivity = false;
         mService.reportNetworkConnectivity(n, noConnectivity);
@@ -10215,6 +10317,54 @@
         // Wait for onNetworkConnectivityReported to fire
         verify(mConnectivityDiagnosticsCallback)
                 .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
+
+        // Also expect a ConnectivityReport after NetworkMonitor asynchronously re-validates
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS).times(2))
+                .onConnectivityReportAvailable(
+                        argThat(report ->
+                                areConnDiagCapsRedacted(report.getNetworkCapabilities())));
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReportedSeparateUid()
+            throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        // reset to ignore callbacks from setup
+        reset(mConnectivityDiagnosticsCallback);
+
+        // report known Connectivity from a different uid. Verify that network is not re-validated
+        // and this callback is not notified.
+        final Network n = mCellNetworkAgent.getNetwork();
+        final boolean hasConnectivity = true;
+        doAsUid(Process.myUid() + 1, () -> mService.reportNetworkConnectivity(n, hasConnectivity));
+
+        // Block until all other events are done processing.
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Verify onNetworkConnectivityReported did not fire
+        verify(mConnectivityDiagnosticsCallback, never())
+                .onNetworkConnectivityReported(any(), anyBoolean());
+        verify(mConnectivityDiagnosticsCallback, never())
+                .onConnectivityReportAvailable(any());
+
+        // report different Connectivity from a different uid. Verify that network is re-validated
+        // and that this callback is notified.
+        final boolean noConnectivity = false;
+        doAsUid(Process.myUid() + 1, () -> mService.reportNetworkConnectivity(n, noConnectivity));
+
+        // Block until all other events are done processing.
+        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Wait for onNetworkConnectivityReported to fire
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
+
+        // Also expect a ConnectivityReport after NetworkMonitor asynchronously re-validates
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onConnectivityReportAvailable(
+                        argThat(report ->
+                                areConnDiagCapsRedacted(report.getNetworkCapabilities())));
     }
 
     @Test
@@ -13359,43 +13509,43 @@
     }
 
     @Test
-    public void testSetMobileDataPreferredUids_noIssueToFactory() throws Exception {
-        // First set mobile data preferred uid to create a multi-layer requests: 1. listen for
+    public void testMultilayerRequestsOfSetMobileDataPreferredUids() throws Exception {
+        // First set mobile data preferred uid to create a multi-layer requests: 1. request for
         // cellular, 2. track the default network for fallback.
         setAndUpdateMobileDataPreferredUids(
                 Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
 
         final HandlerThread handlerThread = new HandlerThread("MockFactory");
         handlerThread.start();
-        NetworkCapabilities internetFilter = new NetworkCapabilities()
+        final NetworkCapabilities cellFilter = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(),
-                mServiceContext, "internetFactory", internetFilter, mCsHandlerThread);
-        internetFactory.setScoreFilter(40);
+        final MockNetworkFactory cellFactory = new MockNetworkFactory(handlerThread.getLooper(),
+                mServiceContext, "cellFactory", cellFilter, mCsHandlerThread);
+        cellFactory.setScoreFilter(40);
 
         try {
-            internetFactory.register();
-            // Default internet request only. The first request is listen for cellular network,
-            // which is never sent to factories (it's a LISTEN, not requestable). The second
-            // fallback request is TRACK_DEFAULT which is also not sent to factories.
-            internetFactory.expectRequestAdds(1);
-            internetFactory.assertRequestCountEquals(1);
+            cellFactory.register();
+            // Default internet request and the mobile data preferred request.
+            cellFactory.expectRequestAdds(2);
+            cellFactory.assertRequestCountEquals(2);
 
-            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.connect(true);
+            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            mWiFiNetworkAgent.connect(true);
 
-            // The internet factory however is outscored, and should lose its requests.
-            internetFactory.expectRequestRemove();
-            internetFactory.assertRequestCountEquals(0);
+            // The cellFactory however is outscored, and should lose default internet request.
+            // But it should still see mobile data preferred request.
+            cellFactory.expectRequestRemove();
+            cellFactory.assertRequestCountEquals(1);
 
-            mCellNetworkAgent.disconnect();
+            mWiFiNetworkAgent.disconnect();
             // The network satisfying the default internet request has disconnected, so the
-            // internetFactory sees the default request again.
-            internetFactory.expectRequestAdds(1);
-            internetFactory.assertRequestCountEquals(1);
+            // cellFactory sees the default internet requests again.
+            cellFactory.expectRequestAdd();
+            cellFactory.assertRequestCountEquals(2);
         } finally {
-            internetFactory.terminate();
+            cellFactory.terminate();
             handlerThread.quitSafely();
         }
     }
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index cf2c9c7..5bbbe40 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -58,6 +58,7 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.os.Binder;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.system.Os;
 import android.test.mock.MockContext;
@@ -66,9 +67,11 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.IpSecService.TunnelInterfaceRecord;
+import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -83,6 +86,9 @@
 @SmallTest
 @RunWith(Parameterized.class)
 public class IpSecServiceParameterizedTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
+            Build.VERSION_CODES.R /* ignoreClassUpTo */);
 
     private static final int TEST_SPI = 0xD1201D;
 
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 22a2c94..6957d51 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -30,14 +30,16 @@
 
 import android.content.Context;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.IpSecService.IResource;
 import com.android.server.IpSecService.RefcountedResource;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,7 +53,8 @@
 
 /** Unit tests for {@link IpSecService.RefcountedResource}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecServiceRefcountedResourceTest {
     Context mMockContext;
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 6232423..fabd6f1 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -43,6 +43,7 @@
 import android.net.IpSecSpiResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.os.Binder;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.system.ErrnoException;
@@ -51,7 +52,9 @@
 import android.util.Range;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import dalvik.system.SocketTagger;
 
@@ -70,7 +73,8 @@
 
 /** Unit tests for {@link IpSecService}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpSecServiceTest {
 
     private static final int DROID_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
index 5ec1119..64736f2 100644
--- a/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/unit/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -43,11 +43,13 @@
 import android.net.EthernetManager
 import android.net.NetworkInfo.DetailedState.CONNECTED
 import android.net.NetworkInfo.DetailedState.DISCONNECTED
+import android.os.Build
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
 import com.android.server.ConnectivityService.LegacyTypeTracker
 import com.android.server.connectivity.NetworkAgentInfo
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertSame
@@ -64,8 +66,9 @@
 
 const val UNSUPPORTED_TYPE = TYPE_WIMAX
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class LegacyTypeTrackerTest {
     private val supportedTypes = arrayOf(TYPE_WIFI, TYPE_WIFI_P2P, TYPE_ETHERNET, TYPE_MOBILE,
             TYPE_MOBILE_SUPL, TYPE_MOBILE_MMS, TYPE_MOBILE_SUPL, TYPE_MOBILE_DUN, TYPE_MOBILE_HIPRI,
diff --git a/tests/unit/java/com/android/server/NetIdManagerTest.kt b/tests/unit/java/com/android/server/NetIdManagerTest.kt
index 6f5e740..811134e 100644
--- a/tests/unit/java/com/android/server/NetIdManagerTest.kt
+++ b/tests/unit/java/com/android/server/NetIdManagerTest.kt
@@ -16,17 +16,20 @@
 
 package com.android.server
 
+import android.os.Build
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
 import com.android.server.NetIdManager.MIN_NET_ID
-import com.android.testutils.assertThrows
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
 import kotlin.test.assertEquals
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class NetIdManagerTest {
     @Test
     fun testReserveReleaseNetId() {
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
index 13516d7..ea29da0 100644
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
@@ -38,17 +38,18 @@
 import android.net.NetworkPolicyManager;
 import android.os.BatteryStats;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.internal.app.IBatteryStats;
 import com.android.server.NetworkManagementService.Dependencies;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,8 +65,9 @@
 /**
  * Tests for {@link NetworkManagementService}.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkManagementServiceTest {
     private NetworkManagementService mNMService;
     @Mock private Context mContext;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 1eac4ea..e80a938 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -31,17 +31,19 @@
 import android.content.Context;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.NsdService.DaemonConnection;
 import com.android.server.NsdService.DaemonConnectionSupplier;
 import com.android.server.NsdService.NativeCallbackReceiver;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
@@ -56,8 +58,9 @@
 // TODOs:
 //  - test client can send requests and receive replies
 //  - test NSD_ON ENABLE/DISABLED listening
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NsdServiceTest {
 
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 0ffeec9..9ef558f 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -55,15 +55,17 @@
 import android.net.ResolverParamsParcel;
 import android.net.RouteInfo;
 import android.net.shared.PrivateDnsConfig;
+import android.os.Build;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import libcore.net.InetAddressUtils;
 
@@ -83,8 +85,9 @@
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.connectivity.DnsManagerTest
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class DnsManagerTest {
     static final String TEST_IFACENAME = "test_wlan0";
     static final int TEST_NETID = 100;
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index 45b575a..785153a 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -19,27 +19,28 @@
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
 import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.os.Build
 import android.text.TextUtils
 import android.util.ArraySet
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
 import com.android.server.connectivity.FullScore.MAX_CS_MANAGED_POLICY
 import com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED
 import com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED
 import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
 import com.android.server.connectivity.FullScore.POLICY_IS_VPN
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.Test
 import org.junit.runner.RunWith
-import kotlin.collections.minOfOrNull
-import kotlin.collections.maxOfOrNull
 import kotlin.reflect.full.staticProperties
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class FullScoreTest {
     // Convenience methods
     fun FullScore.withPolicies(
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 70495cc..52b05aa 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -42,11 +42,12 @@
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
 import android.net.metrics.WakeupStats;
+import android.os.Build;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,8 +56,9 @@
 import java.util.List;
 
 // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpConnectivityEventBuilderTest {
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8b072c4..063ccd3 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -45,15 +45,16 @@
 import android.net.metrics.IpReachabilityEvent;
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
+import android.os.Build;
 import android.os.Parcelable;
 import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.internal.util.BitUtils;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,8 +66,9 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class IpConnectivityMetricsTest {
     static final IpReachabilityEvent FAKE_EV =
             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index 36e229d..58a7c89 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -43,14 +43,16 @@
 import android.net.NetworkProvider;
 import android.net.NetworkScore;
 import android.os.Binder;
+import android.os.Build;
 import android.text.format.DateUtils;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.connectivity.resources.R;
 import com.android.server.ConnectivityService;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Before;
@@ -59,8 +61,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class LingerMonitorTest {
     static final String CELLULAR = "CELLULAR";
     static final String WIFI     = "WIFI";
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index 4c80f6a..e2ad00d 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -28,8 +28,7 @@
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
 
-import static junit.framework.TestCase.assertNotNull;
-
+import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -54,6 +53,7 @@
 import android.net.NetworkPolicyManager;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
+import android.os.Build;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -63,13 +63,14 @@
 import android.util.RecurrenceRule;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkStatsManagerInternal;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Before;
@@ -87,8 +88,9 @@
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class MultipathPolicyTrackerTest {
     private static final Network TEST_NETWORK = new Network(123);
     private static final int POLICY_SNOOZED = -100;
diff --git a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
index 9b2a638..f358726 100644
--- a/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/unit/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -40,13 +40,15 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.os.Build;
 import android.os.Handler;
 import android.os.test.TestLooper;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.ConnectivityService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,8 +58,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class Nat464XlatTest {
 
     static final String BASE_IFACE = "test0";
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 50aaaee..7d6c3ae 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -31,14 +31,15 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.os.Build;
 import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,8 +53,9 @@
 import java.util.Comparator;
 import java.util.List;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetdEventListenerServiceTest {
     private static final String EXAMPLE_IPV4 = "192.0.2.1";
     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 3adf08c..07deeef 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,8 +16,17 @@
 
 package com.android.server.connectivity;
 
-import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
 
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.LOST_INTERNET;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.NETWORK_SWITCH;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.NO_INTERNET;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.PARTIAL_CONNECTIVITY;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.PRIVATE_DNS_BROKEN;
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.SIGN_IN;
+
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.clearInvocations;
@@ -39,19 +48,19 @@
 import android.net.ConnectivityResources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.os.Build;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.connectivity.resources.R;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,12 +74,15 @@
 import java.util.Collections;
 import java.util.List;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkNotificationManagerTest {
 
     private static final String TEST_SSID = "Test SSID";
     private static final String TEST_EXTRA_INFO = "extra";
+    private static final int TEST_NOTIF_ID = 101;
+    private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID);
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -132,6 +144,7 @@
         }
         when(mResources.getStringArray(R.array.network_switch_type_name))
             .thenReturn(transportNames);
+        when(mResources.getBoolean(R.bool.config_autoCancelNetworkNotifications)).thenReturn(true);
 
         mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
     }
@@ -228,29 +241,65 @@
         verify(mNotificationManager, never()).notify(any(), anyInt(), any());
     }
 
+    private void assertNotification(NotificationType type, boolean ongoing, boolean autoCancel) {
+        final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
+        mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1)).notify(eq(TEST_NOTIF_TAG), eq(type.eventId),
+                noteCaptor.capture());
+
+        assertEquals("Notification ongoing flag should be " + (ongoing ? "set" : "unset"),
+                ongoing, (noteCaptor.getValue().flags & FLAG_ONGOING_EVENT) != 0);
+        assertEquals("Notification autocancel flag should be " + (autoCancel ? "set" : "unset"),
+                autoCancel, (noteCaptor.getValue().flags & FLAG_AUTO_CANCEL) != 0);
+    }
+
     @Test
     public void testDuplicatedNotificationsNoInternetThenSignIn() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
-
         // Show first NO_INTERNET
-        mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false);
-        verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any());
+        assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */);
 
         // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
-        mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId));
-        verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any());
+        assertNotification(SIGN_IN, false /* ongoing */, true /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
 
         // Network disconnects
-        mManager.clearNotification(id);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId));
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
+    }
+
+    @Test
+    public void testOngoingSignInNotification() {
+        doReturn(true).when(mResources).getBoolean(R.bool.config_ongoingSignInNotification);
+
+        // Show first NO_INTERNET
+        assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */);
+
+        // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
+        assertNotification(SIGN_IN, true /* ongoing */, true /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
+
+        // Network disconnects
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
+    }
+
+    @Test
+    public void testNoAutoCancelNotification() {
+        doReturn(false).when(mResources).getBoolean(R.bool.config_autoCancelNetworkNotifications);
+
+        // Show NO_INTERNET, then SIGN_IN
+        assertNotification(NO_INTERNET, false /* ongoing */, false /* autoCancel */);
+        assertNotification(SIGN_IN, false /* ongoing */, false /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
+
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
     }
 
     @Test
     public void testDuplicatedNotificationsSignInThenNoInternet() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
+        final int id = TEST_NOTIF_ID;
+        final String tag = TEST_NOTIF_TAG;
 
         // Show first SIGN_IN
         mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
@@ -269,8 +318,8 @@
 
     @Test
     public void testClearNotificationByType() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
+        final int id = TEST_NOTIF_ID;
+        final String tag = TEST_NOTIF_TAG;
 
         // clearNotification(int id, NotificationType notifyType) will check if given type is equal
         // to previous type or not. If they are equal then clear the notification; if they are not
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
index 409f8c3..d03c567 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
@@ -20,8 +20,10 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkRequest
 import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import android.os.Build
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
@@ -32,8 +34,9 @@
 
 const val POLICY_NONE = 0L
 
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 class NetworkOfferTest {
     val mockCallback = mock(INetworkOfferCallback::class.java)
 
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6bf6cc5..8f46508 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -80,7 +80,9 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -96,10 +98,12 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class PermissionMonitorTest {
     private static final UserHandle MOCK_USER1 = UserHandle.of(0);
     private static final UserHandle MOCK_USER2 = UserHandle.of(1);
@@ -208,16 +212,12 @@
         return packageInfo;
     }
 
-    private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid,
-            UserHandle user) {
+    private static PackageInfo buildPackageInfo(String packageName, int uid,
+            String... permissions) {
         final PackageInfo pkgInfo;
-        if (hasSystemPermission) {
-            pkgInfo = systemPackageInfoWithPermissions(
-                    CHANGE_NETWORK_STATE, NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
-        } else {
-            pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, "");
-        }
-        pkgInfo.applicationInfo.uid = user.getUid(UserHandle.getAppId(uid));
+        pkgInfo = systemPackageInfoWithPermissions(permissions);
+        pkgInfo.packageName = packageName;
+        pkgInfo.applicationInfo.uid = uid;
         return pkgInfo;
     }
 
@@ -603,15 +603,12 @@
     @Test
     public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
-                Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1),
-                        buildPackageInfo(false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1),
-                        buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
-                }));
-        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
-                eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
-                buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
+                List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_UID1, CHANGE_NETWORK_STATE,
+                                CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+                        buildPackageInfo(MOCK_PACKAGE1, MOCK_UID1),
+                        buildPackageInfo(MOCK_PACKAGE2, MOCK_UID2),
+                        buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)));
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1);
         mPermissionMonitor.startMonitoring();
         // Every app on user 0 except MOCK_UID2 are under VPN.
         final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
@@ -655,13 +652,10 @@
     @Test
     public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
         when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
-                Arrays.asList(new PackageInfo[] {
-                        buildPackageInfo(true /* hasSystemPermission */, SYSTEM_UID1, MOCK_USER1),
-                        buildPackageInfo(false /* hasSystemPermission */, VPN_UID, MOCK_USER1)
-                }));
-        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1),
-                eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
-                buildPackageInfo(false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1));
+                List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_UID1, CHANGE_NETWORK_STATE,
+                                NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
+                        buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)));
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1);
 
         mPermissionMonitor.startMonitoring();
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
@@ -898,13 +892,26 @@
                 new int[]{ MOCK_UID2 });
     }
 
+    private BroadcastReceiver expectBroadcastReceiver(String... actions) {
+        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(),
+                argThat(filter -> {
+                    for (String action : actions) {
+                        if (!filter.hasAction(action)) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }), any(), any());
+        return receiverCaptor.getValue();
+    }
+
     @Test
     public void testIntentReceiver() throws Exception {
         final NetdServiceMonitor netdServiceMonitor = new NetdServiceMonitor(mNetdService);
-        final ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext, times(1)).registerReceiver(receiverCaptor.capture(), any(), any(), any());
-        final BroadcastReceiver receiver = receiverCaptor.getValue();
+        final BroadcastReceiver receiver = expectBroadcastReceiver(
+                Intent.ACTION_PACKAGE_ADDED, Intent.ACTION_PACKAGE_REMOVED);
 
         // Verify receiving PACKAGE_ADDED intent.
         final Intent addedIntent = new Intent(Intent.ACTION_PACKAGE_ADDED,
@@ -1058,4 +1065,136 @@
         netdMonitor.expectNoPermission(
                 new UserHandle[]{MOCK_USER2}, new int[]{ MOCK_UID1, MOCK_UID2 });
     }
+
+    @Test
+    public void testOnExternalApplicationsAvailable() throws Exception {
+        final NetdServiceMonitor netdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        final NetdMonitor netdMonitor = new NetdMonitor(mNetdService);
+        final BroadcastReceiver receiver = expectBroadcastReceiver(
+                Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+
+        // Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
+        // and have different uids. There has no permission for both uids.
+        when(mUserManager.getUserHandles(eq(true))).thenReturn(List.of(MOCK_USER1));
+        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID1),
+                        buildPackageInfo(MOCK_PACKAGE2, MOCK_UID2)));
+        mPermissionMonitor.startMonitoring();
+        netdMonitor.expectNoPermission(
+                new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2});
+        netdServiceMonitor.expectPermission(
+                INetd.PERMISSION_NONE, new int[]{MOCK_UID1, MOCK_UID2});
+
+        // Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
+        final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1,
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, INTERNET);
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID2, CHANGE_NETWORK_STATE,
+                UPDATE_DEVICE_STATS);
+        receiver.onReceive(mContext, externalIntent);
+        netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+        netdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[] { MOCK_UID1 });
+        netdServiceMonitor.expectPermission(
+                INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID2});
+    }
+
+    @Test
+    public void testOnExternalApplicationsAvailable_AppsNotRegisteredOnStartMonitoring()
+            throws Exception {
+        final NetdServiceMonitor netdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        final NetdMonitor netdMonitor = new NetdMonitor(mNetdService);
+        final BroadcastReceiver receiver = expectBroadcastReceiver(
+                Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+
+        // One user MOCK_USER1
+        mPermissionMonitor.onUserAdded(MOCK_USER1);
+
+        // Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
+        // and have different uids. There has no permission for both uids.
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1,
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, INTERNET);
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID2, CHANGE_NETWORK_STATE,
+                UPDATE_DEVICE_STATS);
+
+        // Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
+        final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+                new String[] { MOCK_PACKAGE1 , MOCK_PACKAGE2});
+        receiver.onReceive(mContext, externalIntent);
+        netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
+        netdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[] { MOCK_UID1 });
+        netdServiceMonitor.expectPermission(
+                INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID2});
+    }
+
+    @Test
+    public void testOnExternalApplicationsAvailableWithSharedUid()
+            throws Exception {
+        final NetdServiceMonitor netdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        final NetdMonitor netdMonitor = new NetdMonitor(mNetdService);
+        final BroadcastReceiver receiver = expectBroadcastReceiver(
+                Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+
+        // Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
+        // storage and shared on MOCK_UID1. There has no permission for MOCK_UID1.
+        when(mUserManager.getUserHandles(eq(true))).thenReturn(List.of(MOCK_USER1));
+        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID1),
+                        buildPackageInfo(MOCK_PACKAGE2, MOCK_UID1)));
+        mPermissionMonitor.startMonitoring();
+        netdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[] {MOCK_UID1});
+
+        // Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
+        final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[] {MOCK_PACKAGE1});
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1, CHANGE_NETWORK_STATE);
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID1, UPDATE_DEVICE_STATS);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1))
+                .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+        receiver.onReceive(mContext, externalIntent);
+        netdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdServiceMonitor.expectPermission(
+                INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[] {MOCK_UID1});
+    }
+
+    @Test
+    public void testOnExternalApplicationsAvailableWithSharedUid_DifferentStorage()
+            throws Exception {
+        final NetdServiceMonitor netdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        final NetdMonitor netdMonitor = new NetdMonitor(mNetdService);
+        final BroadcastReceiver receiver = expectBroadcastReceiver(
+                Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+
+        // Initial the permission state. MOCK_PACKAGE1 is installed on external storage and
+        // MOCK_PACKAGE2 is installed on device. These two packages are shared on MOCK_UID1.
+        // MOCK_UID1 has NETWORK and INTERNET permissions.
+        when(mUserManager.getUserHandles(eq(true))).thenReturn(List.of(MOCK_USER1));
+        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID1),
+                        buildPackageInfo(MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE,
+                                INTERNET)));
+        mPermissionMonitor.startMonitoring();
+        netdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[] {MOCK_UID1});
+
+        // Verify receiving EXTERNAL_APPLICATIONS_AVAILABLE intent and update permission to netd.
+        final Intent externalIntent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        externalIntent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, new String[] {MOCK_PACKAGE1});
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID1,
+                CONNECTIVITY_USE_RESTRICTED_NETWORKS, UPDATE_DEVICE_STATS);
+        buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE,
+                INTERNET);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1))
+                .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+        receiver.onReceive(mContext, externalIntent);
+        netdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
+        netdServiceMonitor.expectPermission(
+                INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                new int[] {MOCK_UID1});
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 6ff47ae..b706090 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -97,13 +97,14 @@
 import android.util.Range;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.server.IpSecService;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -138,8 +139,9 @@
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.connectivity.VpnTest
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
 public class VpnTest {
     private static final String TAG = "VpnTest";
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
index 8b730af..03d9404 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -25,12 +25,14 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Before;
@@ -39,8 +41,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsAccessTest {
     private static final String TEST_PKG = "com.example.test";
     private static final int TEST_UID = 12345;
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
index 505ff9b..e771558 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -42,6 +42,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.SubscriptionPlan;
@@ -51,9 +52,10 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -79,8 +81,9 @@
 /**
  * Tests for {@link NetworkStatsCollection}.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsCollectionTest {
 
     private static final String TEST_FILE = "test.bin";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 40d4446..8d7aa4e 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -37,12 +37,14 @@
 import android.net.NetworkStats;
 import android.net.TrafficStats;
 import android.net.UnderlyingNetworkInfo;
+import android.os.Build;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -60,8 +62,9 @@
 import java.io.OutputStream;
 
 /** Tests for {@link NetworkStatsFactory}. */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
     private static final String CLAT_PREFIX = "v4-";
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 9fa1c50..e35104e 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -39,6 +39,7 @@
 import android.net.NetworkIdentity;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -51,9 +52,10 @@
 import android.util.ArrayMap;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.net.NetworkStatsServiceTest.LatchedHandler;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
@@ -68,8 +70,9 @@
 /**
  * Tests for {@link NetworkStatsObservers}.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsObserversTest {
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test1";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index ee94ae9..ab76460 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -98,6 +98,7 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -114,12 +115,13 @@
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TestableNetworkStatsProviderBinder;
 
@@ -146,8 +148,9 @@
  * TODO: This test used to be really brittle because it used Easymock - it uses Mockito now, but
  * still uses the Easymock structure, which could be simplified.
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkStatsServiceTest extends NetworkStatsBaseTest {
     private static final String TAG = "NetworkStatsServiceTest";
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 6d2c7dc..2bc385c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -34,6 +34,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.NetworkTemplate;
+import android.os.Build;
 import android.os.test.TestLooper;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
@@ -43,11 +44,12 @@
 
 import com.android.internal.util.CollectionUtils;
 import com.android.server.net.NetworkStatsSubscriptionsMonitor.RatTypeListener;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -57,7 +59,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
-@RunWith(JUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public final class NetworkStatsSubscriptionsMonitorTest {
     private static final int TEST_SUBID1 = 3;
     private static final int TEST_SUBID2 = 5;
diff --git a/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index ebbc0ef..5f3efed 100644
--- a/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/unit/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -20,9 +20,12 @@
 
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk;
+import android.os.Build;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,7 +37,8 @@
 
 /** Unit tests for {@link NetworkAttributes}. */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class NetworkAttributesTest {
     private static final String WEIGHT_FIELD_NAME_PREFIX = "WEIGHT_";
     private static final float EPSILON = 0.0001f;