Merge "Mark HostsideConnOnActivityStartTest#testStartActivity_doze as flaky." into tm-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c4c79c6..7eb935e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -34,6 +34,18 @@
         }
       ]
     },
+    // CTS tests that target older SDKs.
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk31",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
     {
       "name": "bpf_existence_test"
     },
@@ -94,6 +106,17 @@
       ]
     },
     {
+      "name": "CtsNetTestCasesMaxTargetSdk31[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
       "name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
     },
     {
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 1728665..2c7b868 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -21,7 +21,7 @@
 java_defaults {
     name: "TetheringApiLevel",
     sdk_version: "module_current",
-    target_sdk_version: "31",
+    target_sdk_version: "33",
     min_sdk_version: "30",
 }
 
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
index 40eed3f..904e491 100644
--- a/Tethering/jarjar-rules.txt
+++ b/Tethering/jarjar-rules.txt
@@ -4,6 +4,7 @@
 # module will be overwritten by the ones in the framework.
 rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
 rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+rule android.util.IndentingPrintWriter* com.android.networkstack.tethering.util.AndroidUtilIndentingPrintWriter@1
 
 rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
 
@@ -13,4 +14,7 @@
 # Classes from net-utils-device-common
 rule com.android.net.module.util.Struct* com.android.networkstack.tethering.util.Struct@1
 
-rule com.google.protobuf.** com.android.networkstack.tethering.protobuf@1
\ No newline at end of file
+rule com.google.protobuf.** com.android.networkstack.tethering.protobuf@1
+
+# Classes for hardware offload hidl interface
+rule android.hidl.base.V1_0.DebugInfo* com.android.networkstack.tethering.hidl.base.V1_0.DebugInfo@1
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index a4d0448..31c3df3 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -49,7 +49,7 @@
 // Use with NetworkStackJarJarRules.
 android_library {
     name: "TetheringIntegrationTestsLatestSdkLib",
-    target_sdk_version: "31",
+    target_sdk_version: "33",
     platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
     visibility: [
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 3699f7a..5869f2b 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -36,9 +36,11 @@
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.testutils.DeviceInfoUtils.KVersion;
 import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -1067,23 +1069,43 @@
                 false /* usingBpf */);
     }
 
-    private static boolean isUdpOffloadSupportedByKernel() {
-        final String kVersionString = VintfRuntimeInfo.getKernelVersion();
-        // Kernel version which is older than 4.14 doesn't support UDP offload absolutely. Kernel
-        // version which is between 4.14 and 5.8 support UDP offload probably. Simply apply kernel
-        // 4.14 to be threshold first and monitor on what devices tests fail for improving the
-        // offload support checking.
-        return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.14") >= 0;
+    private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+        final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+        return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+                || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+                || current.isAtLeast(new KVersion(5, 4, 98));
     }
 
     @Test
+    public void testIsUdpOffloadSupportedByKernel() throws Exception {
+        assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+        assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+        assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+        assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+        assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+    }
+
+    // TODO: refactor test testTetherUdpV4* into IPv4 UDP non-offload and offload tests.
+    // That can be easier to know which feature is verified from test results.
+    @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)
     public void testTetherUdpV4AfterR() throws Exception {
         initializeTethering();
-        boolean usingBpf = isUdpOffloadSupportedByKernel();
+        final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+        boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion);
         if (!usingBpf) {
             Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
-                    + VintfRuntimeInfo.getKernelVersion());
+                    + kernelVersion);
         }
         runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
                 usingBpf);
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index 18fd63b..a84fdd2 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -22,7 +22,7 @@
     name: "MtsTetheringTestLatestSdk",
 
     min_sdk_version: "30",
-    target_sdk_version: "31",
+    target_sdk_version: "33",
 
     libs: [
         "android.test.base",
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 37b1bc8..fd1166c 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -90,7 +90,7 @@
     static_libs: [
         "TetheringApiStableLib",
     ],
-    target_sdk_version: "31",
+    target_sdk_version: "33",
     visibility: [
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 14fcdd6..706dd1d 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -133,6 +133,9 @@
     LOW_POWER_STANDBY_MATCH = (1 << 6),
     IIF_MATCH = (1 << 7),
     LOCKDOWN_VPN_MATCH = (1 << 8),
+    OEM_DENY_1_MATCH = (1 << 9),
+    OEM_DENY_2_MATCH = (1 << 10),
+    OEM_DENY_3_MATCH = (1 << 11),
 };
 
 enum BpfPermissionMatch {
@@ -147,9 +150,9 @@
     SELECT_MAP_B,
 };
 
-// TODO: change the configuration object from an 8-bit bitmask to an object with clearer
+// TODO: change the configuration object from a bitmask to an object with clearer
 // semantics, like a struct.
-typedef uint8_t BpfConfig;
+typedef uint32_t BpfConfig;
 static const BpfConfig DEFAULT_CONFIG = 0;
 
 typedef struct {
@@ -160,7 +163,9 @@
 } UidOwnerValue;
 STRUCT_SIZE(UidOwnerValue, 2 * 4);  // 8
 
+// Entry in the configuration map that stores which UID rules are enabled.
 #define UID_RULES_CONFIGURATION_KEY 1
+// Entry in the configuration map that stores which stats map is currently in use.
 #define CURRENT_STATS_MAP_CONFIGURATION_KEY 2
 
 #define BPF_CLATD_PATH "/sys/fs/bpf/net_shared/"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 33381d7..d754616 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -62,7 +62,7 @@
 DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
 DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
                    AID_NET_BW_ACCT)
-DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint32_t, CONFIGURATION_MAP_SIZE,
                    AID_NET_BW_ACCT)
 DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
                    AID_NET_BW_ACCT)
@@ -216,6 +216,15 @@
         if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
             return BPF_DROP;
         }
+        if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
+            return BPF_DROP;
+        }
+        if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
+            return BPF_DROP;
+        }
+        if ((enabledRules & OEM_DENY_3_MATCH) && (uidRules & OEM_DENY_3_MATCH)) {
+            return BPF_DROP;
+        }
     }
     if (direction == BPF_INGRESS && skb->ifindex != 1) {
         if (uidRules & IIF_MATCH) {
@@ -234,7 +243,7 @@
 }
 
 static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
-                                                            StatsKey* key, uint8_t selectedMap) {
+                                                            StatsKey* key, uint32_t selectedMap) {
     if (selectedMap == SELECT_MAP_A) {
         update_stats_map_A(skb, direction, key);
     } else if (selectedMap == SELECT_MAP_B) {
@@ -286,7 +295,7 @@
     if (counterSet) key.counterSet = (uint32_t)*counterSet;
 
     uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
-    uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+    uint32_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
 
     // Use asm("%0 &= 1" : "+r"(match)) before return match,
     // to help kernel's bpf verifier, so that it can be 100% certain
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 51ff5ec..0bb98f8 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -1300,6 +1300,17 @@
     }
 
     /**
+     * Removes the interface name from all entries.
+     * This mutates the original structure in place.
+     * @hide
+     */
+    public void clearInterfaces() {
+        for (int i = 0; i < size; i++) {
+            iface[i] = null;
+        }
+    }
+
+    /**
      * Only keep entries that match all specified filters.
      *
      * <p>This mutates the original structure in place. After this method is called,
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 33b44c8..f19bf4a 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -312,9 +312,12 @@
             @Override
             public void onAvailable(@NonNull Network network) {
                 final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
-                        network, mBaseListener);
+                        network, mBaseListener, mBaseExecutor);
                 mPerNetworkListeners.put(network, wrappedListener);
-                discoverServices(mServiceType, mProtocolType, network, mBaseExecutor,
+                // Run discovery callbacks inline on the service handler thread, which is the
+                // same thread used by this NetworkCallback, but DelegatingDiscoveryListener will
+                // use the base executor to run the wrapped callbacks.
+                discoverServices(mServiceType, mProtocolType, network, Runnable::run,
                         wrappedListener);
             }
 
@@ -334,7 +337,8 @@
         public void start(@NonNull NetworkRequest request) {
             final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
             cm.registerNetworkCallback(request, mNetworkCb, mHandler);
-            mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType));
+            mHandler.post(() -> mBaseExecutor.execute(() ->
+                    mBaseListener.onDiscoveryStarted(mServiceType)));
         }
 
         /**
@@ -351,7 +355,7 @@
                 final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
                 cm.unregisterNetworkCallback(mNetworkCb);
                 if (mPerNetworkListeners.size() == 0) {
-                    mBaseListener.onDiscoveryStopped(mServiceType);
+                    mBaseExecutor.execute(() -> mBaseListener.onDiscoveryStopped(mServiceType));
                     return;
                 }
                 for (int i = 0; i < mPerNetworkListeners.size(); i++) {
@@ -399,14 +403,23 @@
             }
         }
 
+        /**
+         * A listener wrapping calls to an app-provided listener, while keeping track of found
+         * services, so they can all be reported lost when the underlying network is lost.
+         *
+         * This should be registered to run on the service handler.
+         */
         private class DelegatingDiscoveryListener implements DiscoveryListener {
             private final Network mNetwork;
             private final DiscoveryListener mWrapped;
+            private final Executor mWrappedExecutor;
             private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
 
-            private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) {
+            private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
+                    Executor executor) {
                 mNetwork = network;
                 mWrapped = listener;
+                mWrappedExecutor = executor;
             }
 
             void notifyAllServicesLost() {
@@ -415,7 +428,7 @@
                     final NsdServiceInfo serviceInfo = new NsdServiceInfo(
                             trackedInfo.mServiceName, trackedInfo.mServiceType);
                     serviceInfo.setNetwork(mNetwork);
-                    mWrapped.onServiceLost(serviceInfo);
+                    mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
                 }
             }
 
@@ -444,7 +457,7 @@
                     // Do not report onStopDiscoveryFailed when some underlying listeners failed:
                     // this does not mean that all listeners did, and onStopDiscoveryFailed is not
                     // actionable anyway. Just report that discovery stopped.
-                    mWrapped.onDiscoveryStopped(serviceType);
+                    mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
                 }
             }
 
@@ -452,20 +465,20 @@
             public void onDiscoveryStopped(String serviceType) {
                 mPerNetworkListeners.remove(mNetwork);
                 if (mStopRequested && mPerNetworkListeners.size() == 0) {
-                    mWrapped.onDiscoveryStopped(serviceType);
+                    mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
                 }
             }
 
             @Override
             public void onServiceFound(NsdServiceInfo serviceInfo) {
                 mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
-                mWrapped.onServiceFound(serviceInfo);
+                mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
             }
 
             @Override
             public void onServiceLost(NsdServiceInfo serviceInfo) {
                 mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
-                mWrapped.onServiceLost(serviceInfo);
+                mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
             }
         }
     }
@@ -648,8 +661,12 @@
 
         @Override
         public void handleMessage(Message message) {
+            // Do not use message in the executor lambdas, as it will be recycled once this method
+            // returns. Keep references to its content instead.
             final int what = message.what;
+            final int errorCode = message.arg1;
             final int key = message.arg2;
+            final Object obj = message.obj;
             final Object listener;
             final NsdServiceInfo ns;
             final Executor executor;
@@ -659,7 +676,7 @@
                 executor = mExecutorMap.get(key);
             }
             if (listener == null) {
-                Log.d(TAG, "Stale key " + message.arg2);
+                Log.d(TAG, "Stale key " + key);
                 return;
             }
             if (DBG) {
@@ -667,28 +684,28 @@
             }
             switch (what) {
                 case DISCOVER_SERVICES_STARTED:
-                    final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+                    final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
                     executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
                     break;
                 case DISCOVER_SERVICES_FAILED:
                     removeListener(key);
                     executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
-                            getNsdServiceInfoType(ns), message.arg1));
+                            getNsdServiceInfoType(ns), errorCode));
                     break;
                 case SERVICE_FOUND:
                     executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
-                            (NsdServiceInfo) message.obj));
+                            (NsdServiceInfo) obj));
                     break;
                 case SERVICE_LOST:
                     executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
-                            (NsdServiceInfo) message.obj));
+                            (NsdServiceInfo) obj));
                     break;
                 case STOP_DISCOVERY_FAILED:
                     // TODO: failure to stop discovery should be internal and retried internally, as
                     // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
                     removeListener(key);
                     executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
-                            getNsdServiceInfoType(ns), message.arg1));
+                            getNsdServiceInfoType(ns), errorCode));
                     break;
                 case STOP_DISCOVERY_SUCCEEDED:
                     removeListener(key);
@@ -698,33 +715,33 @@
                 case REGISTER_SERVICE_FAILED:
                     removeListener(key);
                     executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
-                            ns, message.arg1));
+                            ns, errorCode));
                     break;
                 case REGISTER_SERVICE_SUCCEEDED:
                     executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
-                            (NsdServiceInfo) message.obj));
+                            (NsdServiceInfo) obj));
                     break;
                 case UNREGISTER_SERVICE_FAILED:
                     removeListener(key);
                     executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
-                            ns, message.arg1));
+                            ns, errorCode));
                     break;
                 case UNREGISTER_SERVICE_SUCCEEDED:
                     // TODO: do not unregister listener until service is unregistered, or provide
                     // alternative way for unregistering ?
-                    removeListener(message.arg2);
+                    removeListener(key);
                     executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
                             ns));
                     break;
                 case RESOLVE_SERVICE_FAILED:
                     removeListener(key);
                     executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
-                            ns, message.arg1));
+                            ns, errorCode));
                     break;
                 case RESOLVE_SERVICE_SUCCEEDED:
                     removeListener(key);
                     executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
-                            (NsdServiceInfo) message.obj));
+                            (NsdServiceInfo) obj));
                     break;
                 default:
                     Log.d(TAG, "Ignored " + message);
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 2621594..200c808 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -53,6 +53,8 @@
     @Nullable
     private Network mNetwork;
 
+    private int mInterfaceIndex;
+
     public NsdServiceInfo() {
     }
 
@@ -312,8 +314,11 @@
     /**
      * Get the network where the service can be found.
      *
-     * This is never null if this {@link NsdServiceInfo} was obtained from
-     * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}.
+     * This is set if this {@link NsdServiceInfo} was obtained from
+     * {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}, unless the service
+     * was found on a network interface that does not have a {@link Network} (such as a tethering
+     * downstream, where services are advertised from devices connected to this device via
+     * tethering).
      */
     @Nullable
     public Network getNetwork() {
@@ -329,6 +334,26 @@
         mNetwork = network;
     }
 
+    /**
+     * Get the index of the network interface where the service was found.
+     *
+     * This is only set when the service was found on an interface that does not have a usable
+     * Network, in which case {@link #getNetwork()} returns null.
+     * @return The interface index as per {@link java.net.NetworkInterface#getIndex}, or 0 if unset.
+     * @hide
+     */
+    public int getInterfaceIndex() {
+        return mInterfaceIndex;
+    }
+
+    /**
+     * Set the index of the network interface where the service was found.
+     * @hide
+     */
+    public void setInterfaceIndex(int interfaceIndex) {
+        mInterfaceIndex = interfaceIndex;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -375,6 +400,7 @@
         }
 
         dest.writeParcelable(mNetwork, 0);
+        dest.writeInt(mInterfaceIndex);
     }
 
     /** Implement the Parcelable interface */
@@ -405,6 +431,7 @@
                     info.mTxtRecord.put(in.readString(), valueArray);
                 }
                 info.mNetwork = in.readParcelable(null, Network.class);
+                info.mInterfaceIndex = in.readInt();
                 return info;
             }
 
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 4ecc8a1..f741c2b 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -992,6 +992,27 @@
      */
     public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
 
+    /**
+     * Firewall chain used for OEM-specific application restrictions.
+     * Denylist of apps that will not have network access due to OEM-specific restrictions.
+     * @hide
+     */
+    public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
+
+    /**
+     * Firewall chain used for OEM-specific application restrictions.
+     * Denylist of apps that will not have network access due to OEM-specific restrictions.
+     * @hide
+     */
+    public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
+
+    /**
+     * Firewall chain used for OEM-specific application restrictions.
+     * Denylist of apps that will not have network access due to OEM-specific restrictions.
+     * @hide
+     */
+    public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
@@ -1000,7 +1021,10 @@
         FIREWALL_CHAIN_POWERSAVE,
         FIREWALL_CHAIN_RESTRICTED,
         FIREWALL_CHAIN_LOW_POWER_STANDBY,
-        FIREWALL_CHAIN_LOCKDOWN_VPN
+        FIREWALL_CHAIN_LOCKDOWN_VPN,
+        FIREWALL_CHAIN_OEM_DENY_1,
+        FIREWALL_CHAIN_OEM_DENY_2,
+        FIREWALL_CHAIN_OEM_DENY_3
     })
     public @interface FirewallChain {}
     // LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h)
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 2ede1c1..05b9ebc 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -62,7 +62,7 @@
     BpfMap<uint64_t, UidTagValue> mCookieTagMap;
     BpfMap<StatsKey, StatsValue> mStatsMapA;
     BpfMap<StatsKey, StatsValue> mStatsMapB;
-    BpfMap<uint32_t, uint8_t> mConfigurationMap;
+    BpfMap<uint32_t, uint32_t> mConfigurationMap;
     BpfMap<uint32_t, uint8_t> mUidPermissionMap;
 
     std::mutex mMutex;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index cd6b565..1bd222d 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -21,6 +21,7 @@
 
 #include <gtest/gtest.h>
 
+#define TEST_BPF_MAP
 #include "BpfHandler.h"
 
 using namespace android::bpf;  // NOLINT(google-build-using-namespace): exempted
@@ -48,46 +49,38 @@
     BpfHandler mBh;
     BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
     BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
-    BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
     BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
 
     void SetUp() {
         std::lock_guard guard(mBh.mMutex);
         ASSERT_EQ(0, setrlimitForTest());
 
-        mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
-                                          TEST_MAP_SIZE, 0));
+        mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeCookieTagMap);
 
-        mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
-                                       TEST_MAP_SIZE, 0));
+        mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeStatsMapA);
 
-        mFakeConfigurationMap.reset(
-                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+        mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
         ASSERT_VALID(mFakeConfigurationMap);
 
-        mFakeUidPermissionMap.reset(
-                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+        mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
         ASSERT_VALID(mFakeUidPermissionMap);
 
-        mBh.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+        mBh.mCookieTagMap = mFakeCookieTagMap;
         ASSERT_VALID(mBh.mCookieTagMap);
-        mBh.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+        mBh.mStatsMapA = mFakeStatsMapA;
         ASSERT_VALID(mBh.mStatsMapA);
-        mBh.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+        mBh.mConfigurationMap = mFakeConfigurationMap;
         ASSERT_VALID(mBh.mConfigurationMap);
         // Always write to stats map A by default.
         ASSERT_RESULT_OK(mBh.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
                                                           SELECT_MAP_A, BPF_ANY));
-        mBh.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+        mBh.mUidPermissionMap = mFakeUidPermissionMap;
         ASSERT_VALID(mBh.mUidPermissionMap);
     }
 
-    int dupFd(const android::base::unique_fd& mapFd) {
-        return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
-    }
-
     int setUpSocketAndTag(int protocol, uint64_t* cookie, uint32_t tag, uid_t uid,
                           uid_t realUid) {
         int sock = socket(protocol, SOCK_STREAM | SOCK_CLOEXEC, 0);
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 4d605ce..9ebef4d 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -193,7 +193,7 @@
         return ret;
     }
 
-    BpfMapRO<uint32_t, uint8_t> configurationMap(CONFIGURATION_MAP_PATH);
+    BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
     if (!configurationMap.isValid()) {
         int ret = -errno;
         ALOGE("get configuration map fd failed: %s", strerror(errno));
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 6def44f..95e6114 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
+import android.net.INetd;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.mdns.aidl.DiscoveryInfo;
@@ -466,7 +467,7 @@
                             // interfaces that do not have an associated Network.
                             break;
                         }
-                        servInfo.setNetwork(new Network(foundNetId));
+                        setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
                         clientInfo.onServiceFound(clientId, servInfo);
                         break;
                     }
@@ -476,10 +477,11 @@
                         final String type = info.registrationType;
                         final int lostNetId = info.netId;
                         servInfo = new NsdServiceInfo(name, type);
-                        // The network could be null if it was torn down when the service is lost
-                        // TODO: avoid returning null in that case, possibly by remembering found
-                        // services on the same interface index and their network at the time
-                        servInfo.setNetwork(lostNetId == 0 ? null : new Network(lostNetId));
+                        // The network could be set to null (netId 0) if it was torn down when the
+                        // service is lost
+                        // TODO: avoid returning null in that case, possibly by remembering
+                        // found services on the same interface index and their network at the time
+                        setServiceNetworkForCallback(servInfo, lostNetId, info.interfaceIdx);
                         clientInfo.onServiceLost(clientId, servInfo);
                         break;
                     }
@@ -557,7 +559,6 @@
                         final GetAddressInfo info = (GetAddressInfo) obj;
                         final String address = info.address;
                         final int netId = info.netId;
-                        final Network network = netId == NETID_UNSET ? null : new Network(netId);
                         InetAddress serviceHost = null;
                         try {
                             serviceHost = InetAddress.getByName(address);
@@ -568,9 +569,10 @@
                         // If the resolved service is on an interface without a network, consider it
                         // as a failure: it would not be usable by apps as they would need
                         // privileged permissions.
-                        if (network != null && serviceHost != null) {
+                        if (netId != NETID_UNSET && serviceHost != null) {
                             clientInfo.mResolvedService.setHost(serviceHost);
-                            clientInfo.mResolvedService.setNetwork(network);
+                            setServiceNetworkForCallback(clientInfo.mResolvedService,
+                                    netId, info.interfaceIdx);
                             clientInfo.onResolveServiceSucceeded(
                                     clientId, clientInfo.mResolvedService);
                         } else {
@@ -590,6 +592,26 @@
        }
     }
 
+    private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
+        switch (netId) {
+            case NETID_UNSET:
+                info.setNetwork(null);
+                break;
+            case INetd.LOCAL_NET_ID:
+                // Special case for LOCAL_NET_ID: Networks on netId 99 are not generally
+                // visible / usable for apps, so do not return it. Store the interface
+                // index instead, so at least if the client tries to resolve the service
+                // with that NsdServiceInfo, it will be done on the same interface.
+                // If they recreate the NsdServiceInfo themselves, resolution would be
+                // done on all interfaces as before T, which should also work.
+                info.setNetwork(null);
+                info.setInterfaceIndex(ifaceIdx);
+                break;
+            default:
+                info.setNetwork(new Network(netId));
+        }
+    }
+
     // The full service name is escaped from standard DNS rules on mdnsresponder, making it suitable
     // for passing to standard system DNS APIs such as res_query() . Thus, make the service name
     // unescape for getting right service address. See "Notes on DNS Name Escaping" on
@@ -767,9 +789,8 @@
         String type = service.getServiceType();
         int port = service.getPort();
         byte[] textRecord = service.getTxtRecord();
-        final Network network = service.getNetwork();
-        final int registerInterface = getNetworkInterfaceIndex(network);
-        if (network != null && registerInterface == IFACE_IDX_ANY) {
+        final int registerInterface = getNetworkInterfaceIndex(service);
+        if (service.getNetwork() != null && registerInterface == IFACE_IDX_ANY) {
             Log.e(TAG, "Interface to register service on not found");
             return false;
         }
@@ -781,10 +802,9 @@
     }
 
     private boolean discoverServices(int discoveryId, NsdServiceInfo serviceInfo) {
-        final Network network = serviceInfo.getNetwork();
         final String type = serviceInfo.getServiceType();
-        final int discoverInterface = getNetworkInterfaceIndex(network);
-        if (network != null && discoverInterface == IFACE_IDX_ANY) {
+        final int discoverInterface = getNetworkInterfaceIndex(serviceInfo);
+        if (serviceInfo.getNetwork() != null && discoverInterface == IFACE_IDX_ANY) {
             Log.e(TAG, "Interface to discover service on not found");
             return false;
         }
@@ -798,9 +818,8 @@
     private boolean resolveService(int resolveId, NsdServiceInfo service) {
         final String name = service.getServiceName();
         final String type = service.getServiceType();
-        final Network network = service.getNetwork();
-        final int resolveInterface = getNetworkInterfaceIndex(network);
-        if (network != null && resolveInterface == IFACE_IDX_ANY) {
+        final int resolveInterface = getNetworkInterfaceIndex(service);
+        if (service.getNetwork() != null && resolveInterface == IFACE_IDX_ANY) {
             Log.e(TAG, "Interface to resolve service on not found");
             return false;
         }
@@ -816,8 +835,17 @@
      * this is to support the legacy mdnsresponder implementation, which historically resolved
      * services on an unspecified network.
      */
-    private int getNetworkInterfaceIndex(Network network) {
-        if (network == null) return IFACE_IDX_ANY;
+    private int getNetworkInterfaceIndex(NsdServiceInfo serviceInfo) {
+        final Network network = serviceInfo.getNetwork();
+        if (network == null) {
+            // Fallback to getInterfaceIndex if present (typically if the NsdServiceInfo was
+            // provided by NsdService from discovery results, and the service was found on an
+            // interface that has no app-usable Network).
+            if (serviceInfo.getInterfaceIndex() != 0) {
+                return serviceInfo.getInterfaceIndex();
+            }
+            return IFACE_IDX_ANY;
+        }
 
         final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
         if (cm == null) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 709b774..1ab7515 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -229,7 +229,7 @@
      */
     protected void broadcastInterfaceStateChange(@NonNull String iface) {
         ensureRunningOnEthernetServiceThread();
-        final int state = mFactory.getInterfaceState(iface);
+        final int state = getInterfaceState(iface);
         final int role = getInterfaceRole(iface);
         final IpConfiguration config = getIpConfigurationForCallback(iface, state);
         final int n = mListeners.beginBroadcast();
@@ -436,15 +436,34 @@
         if (mDefaultInterface != null) {
             removeInterface(mDefaultInterface);
             addInterface(mDefaultInterface);
+            // when this broadcast is sent, any calls to notifyTetheredInterfaceAvailable or
+            // notifyTetheredInterfaceUnavailable have already happened
+            broadcastInterfaceStateChange(mDefaultInterface);
         }
     }
 
+    private int getInterfaceState(final String iface) {
+        if (mFactory.hasInterface(iface)) {
+            return mFactory.getInterfaceState(iface);
+        }
+        if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+            // server mode interfaces are not tracked by the factory.
+            // TODO(b/234743836): interface state for server mode interfaces is not tracked
+            // properly; just return link up.
+            return EthernetManager.STATE_LINK_UP;
+        }
+        return EthernetManager.STATE_ABSENT;
+    }
+
     private int getInterfaceRole(final String iface) {
-        if (!mFactory.hasInterface(iface)) return EthernetManager.ROLE_NONE;
-        final int mode = getInterfaceMode(iface);
-        return (mode == INTERFACE_MODE_CLIENT)
-                ? EthernetManager.ROLE_CLIENT
-                : EthernetManager.ROLE_SERVER;
+        if (mFactory.hasInterface(iface)) {
+            // only client mode interfaces are tracked by the factory.
+            return EthernetManager.ROLE_CLIENT;
+        }
+        if (getInterfaceMode(iface) == INTERFACE_MODE_SERVER) {
+            return EthernetManager.ROLE_SERVER;
+        }
+        return EthernetManager.ROLE_NONE;
     }
 
     private int getInterfaceMode(final String iface) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 64e56a1..42a108f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
@@ -172,6 +173,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
@@ -343,11 +345,16 @@
     @GuardedBy("mStatsLock")
     private String mActiveIface;
 
-    /** Set of any ifaces associated with mobile networks since boot. */
+    /** Set of all ifaces currently associated with mobile networks. */
     private volatile String[] mMobileIfaces = new String[0];
 
-    /** Set of any ifaces associated with wifi networks since boot. */
-    private volatile String[] mWifiIfaces = new String[0];
+    /* A set of all interfaces that have ever been associated with mobile networks since boot. */
+    @GuardedBy("mStatsLock")
+    private final Set<String> mAllMobileIfacesSinceBoot = new ArraySet<>();
+
+    /* A set of all interfaces that have ever been associated with wifi networks since boot. */
+    @GuardedBy("mStatsLock")
+    private final Set<String> mAllWifiIfacesSinceBoot = new ArraySet<>();
 
     /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
     @GuardedBy("mStatsLock")
@@ -1481,9 +1488,9 @@
         // We've been using pure XT stats long enough that we no longer need to
         // splice DEV and XT together.
         final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
-                accessLevel, callingUid, start, end);
+                accessLevel, callingUid, Long.MIN_VALUE, Long.MAX_VALUE);
 
-        final long now = System.currentTimeMillis();
+        final long now = mClock.millis();
         final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
 
         final NetworkStats stats = new NetworkStats(end - start, 1);
@@ -1564,17 +1571,37 @@
         return dataLayer;
     }
 
+    private String[] getAllIfacesSinceBoot(int transport) {
+        synchronized (mStatsLock) {
+            final Set<String> ifaceSet;
+            if (transport == TRANSPORT_WIFI) {
+                ifaceSet = mAllWifiIfacesSinceBoot;
+            } else if (transport == TRANSPORT_CELLULAR) {
+                ifaceSet = mAllMobileIfacesSinceBoot;
+            } else {
+                throw new IllegalArgumentException("Invalid transport " + transport);
+            }
+
+            return ifaceSet.toArray(new String[0]);
+        }
+    }
+
     @Override
     public NetworkStats getUidStatsForTransport(int transport) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
-            final String[] relevantIfaces =
-                    transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+            final String[] ifaceArray = getAllIfacesSinceBoot(transport);
             // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
             // interfaces, so this is not useful, remove it.
             final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
-            return getNetworkStatsUidDetail(ifacesToQuery);
+                    mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
+            final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+            // Clear the interfaces of the stats before returning, so callers won't get this
+            // information. This is because no caller needs this information for now, and it
+            // makes it easier to change the implementation later by using the histories in the
+            // recorder.
+            stats.clearInterfaces();
+            return stats;
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
@@ -1955,7 +1982,6 @@
 
         final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
         final ArraySet<String> mobileIfaces = new ArraySet<>();
-        final ArraySet<String> wifiIfaces = new ArraySet<>();
         for (NetworkStateSnapshot snapshot : snapshots) {
             final int displayTransport =
                     getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
@@ -2000,9 +2026,12 @@
 
                 if (isMobile) {
                     mobileIfaces.add(baseIface);
+                    // If the interface name was present in the wifi set, the interface won't
+                    // be removed from it to prevent stats from getting rollback.
+                    mAllMobileIfacesSinceBoot.add(baseIface);
                 }
                 if (isWifi) {
-                    wifiIfaces.add(baseIface);
+                    mAllWifiIfacesSinceBoot.add(baseIface);
                 }
             }
 
@@ -2044,9 +2073,10 @@
                     findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
                     if (isMobile) {
                         mobileIfaces.add(iface);
+                        mAllMobileIfacesSinceBoot.add(iface);
                     }
                     if (isWifi) {
-                        wifiIfaces.add(iface);
+                        mAllWifiIfacesSinceBoot.add(iface);
                     }
 
                     mStatsFactory.noteStackedIface(iface, baseIface);
@@ -2055,16 +2085,11 @@
         }
 
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
-        mWifiIfaces = wifiIfaces.toArray(new String[0]);
         // TODO (b/192758557): Remove debug log.
         if (CollectionUtils.contains(mMobileIfaces, null)) {
             throw new NullPointerException(
                     "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
         }
-        if (CollectionUtils.contains(mWifiIfaces, null)) {
-            throw new NullPointerException(
-                    "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
-        }
     }
 
     private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -2532,6 +2557,22 @@
             }
             pw.decreaseIndent();
 
+            pw.println("All wifi interfaces:");
+            pw.increaseIndent();
+            for (String iface : mAllWifiIfacesSinceBoot) {
+                pw.print(iface + " ");
+            }
+            pw.println();
+            pw.decreaseIndent();
+
+            pw.println("All mobile interfaces:");
+            pw.increaseIndent();
+            for (String iface : mAllMobileIfacesSinceBoot) {
+                pw.print(iface + " ");
+            }
+            pw.println();
+            pw.decreaseIndent();
+
             // Get the top openSession callers
             final HashMap calls;
             synchronized (mOpenSessionCallsLock) {
diff --git a/service/ServiceConnectivityResources/res/values-or/strings.xml b/service/ServiceConnectivityResources/res/values-or/strings.xml
index 8b85884..49a773a 100644
--- a/service/ServiceConnectivityResources/res/values-or/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-or/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ସିଷ୍ଟମର ସଂଯୋଗ ସମ୍ବନ୍ଧିତ ରିସୋର୍ସଗୁଡ଼ିକ"</string>
+    <string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"ସିଷ୍ଟମ କନେକ୍ଟିଭିଟୀ ରିସୋର୍ସ"</string>
     <string name="wifi_available_sign_in" msgid="8041178343789805553">"ୱାଇ-ଫାଇ ନେଟୱର୍କରେ ସାଇନ୍‍-ଇନ୍‍ କରନ୍ତୁ"</string>
     <string name="network_available_sign_in" msgid="2622520134876355561">"ନେଟ୍‌ୱର୍କରେ ସାଇନ୍‍ ଇନ୍‍ କରନ୍ତୁ"</string>
     <!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
diff --git a/service/ServiceConnectivityResources/res/values-sq/strings.xml b/service/ServiceConnectivityResources/res/values-sq/strings.xml
index 385c75c..85bd84f 100644
--- a/service/ServiceConnectivityResources/res/values-sq/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-sq/strings.xml
@@ -35,7 +35,7 @@
   <string-array name="network_switch_type_name">
     <item msgid="3004933964374161223">"të dhënat celulare"</item>
     <item msgid="5624324321165953608">"Wi-Fi"</item>
-    <item msgid="5667906231066981731">"Bluetooth"</item>
+    <item msgid="5667906231066981731">"Bluetooth-i"</item>
     <item msgid="346574747471703768">"Eternet"</item>
     <item msgid="5734728378097476003">"VPN"</item>
   </string-array>
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 5581c40..4923b00 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -74,6 +74,9 @@
 const char* TrafficController::LOCAL_POWERSAVE = "fw_powersave";
 const char* TrafficController::LOCAL_RESTRICTED = "fw_restricted";
 const char* TrafficController::LOCAL_LOW_POWER_STANDBY = "fw_low_power_standby";
+const char* TrafficController::LOCAL_OEM_DENY_1 = "fw_oem_deny_1";
+const char* TrafficController::LOCAL_OEM_DENY_2 = "fw_oem_deny_2";
+const char* TrafficController::LOCAL_OEM_DENY_3 = "fw_oem_deny_3";
 
 static_assert(BPF_PERMISSION_INTERNET == INetd::PERMISSION_INTERNET,
               "Mismatch between BPF and AIDL permissions: PERMISSION_INTERNET");
@@ -99,6 +102,9 @@
     FLAG_MSG_TRANS(matchType, LOW_POWER_STANDBY_MATCH, match);
     FLAG_MSG_TRANS(matchType, IIF_MATCH, match);
     FLAG_MSG_TRANS(matchType, LOCKDOWN_VPN_MATCH, match);
+    FLAG_MSG_TRANS(matchType, OEM_DENY_1_MATCH, match);
+    FLAG_MSG_TRANS(matchType, OEM_DENY_2_MATCH, match);
+    FLAG_MSG_TRANS(matchType, OEM_DENY_3_MATCH, match);
     if (match) {
         return StringPrintf("Unknown match: %u", match);
     }
@@ -335,6 +341,12 @@
             return ALLOWLIST;
         case LOCKDOWN:
             return DENYLIST;
+        case OEM_DENY_1:
+            return DENYLIST;
+        case OEM_DENY_2:
+            return DENYLIST;
+        case OEM_DENY_3:
+            return DENYLIST;
         case NONE:
         default:
             return DENYLIST;
@@ -363,6 +375,15 @@
         case LOCKDOWN:
             res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
             break;
+        case OEM_DENY_1:
+            res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type);
+            break;
+        case OEM_DENY_2:
+            res = updateOwnerMapEntry(OEM_DENY_2_MATCH, uid, rule, type);
+            break;
+        case OEM_DENY_3:
+            res = updateOwnerMapEntry(OEM_DENY_3_MATCH, uid, rule, type);
+            break;
         case NONE:
         default:
             ALOGW("Unknown child chain: %d", chain);
@@ -440,6 +461,12 @@
         res = replaceRulesInMap(RESTRICTED_MATCH, uids);
     } else if (!name.compare(LOCAL_LOW_POWER_STANDBY)) {
         res = replaceRulesInMap(LOW_POWER_STANDBY_MATCH, uids);
+    } else if (!name.compare(LOCAL_OEM_DENY_1)) {
+        res = replaceRulesInMap(OEM_DENY_1_MATCH, uids);
+    } else if (!name.compare(LOCAL_OEM_DENY_2)) {
+        res = replaceRulesInMap(OEM_DENY_2_MATCH, uids);
+    } else if (!name.compare(LOCAL_OEM_DENY_3)) {
+        res = replaceRulesInMap(OEM_DENY_3_MATCH, uids);
     } else {
         ALOGE("unknown chain name: %s", name.c_str());
         return -EINVAL;
@@ -454,15 +481,15 @@
 int TrafficController::toggleUidOwnerMap(ChildChain chain, bool enable) {
     std::lock_guard guard(mMutex);
     uint32_t key = UID_RULES_CONFIGURATION_KEY;
-    auto oldConfiguration = mConfigurationMap.readValue(key);
-    if (!oldConfiguration.ok()) {
+    auto oldConfigure = mConfigurationMap.readValue(key);
+    if (!oldConfigure.ok()) {
         ALOGE("Cannot read the old configuration from map: %s",
-              oldConfiguration.error().message().c_str());
-        return -oldConfiguration.error().code();
+              oldConfigure.error().message().c_str());
+        return -oldConfigure.error().code();
     }
     Status res;
     BpfConfig newConfiguration;
-    uint8_t match;
+    uint32_t match;
     switch (chain) {
         case DOZABLE:
             match = DOZABLE_MATCH;
@@ -479,11 +506,20 @@
         case LOW_POWER_STANDBY:
             match = LOW_POWER_STANDBY_MATCH;
             break;
+        case OEM_DENY_1:
+            match = OEM_DENY_1_MATCH;
+            break;
+        case OEM_DENY_2:
+            match = OEM_DENY_2_MATCH;
+            break;
+        case OEM_DENY_3:
+            match = OEM_DENY_3_MATCH;
+            break;
         default:
             return -EINVAL;
     }
     newConfiguration =
-            enable ? (oldConfiguration.value() | match) : (oldConfiguration.value() & (~match));
+            enable ? (oldConfigure.value() | match) : (oldConfigure.value() & (~match));
     res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
     if (!isOk(res)) {
         ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
@@ -495,17 +531,17 @@
     std::lock_guard guard(mMutex);
 
     uint32_t key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
-    auto oldConfiguration = mConfigurationMap.readValue(key);
-    if (!oldConfiguration.ok()) {
+    auto oldConfigure = mConfigurationMap.readValue(key);
+    if (!oldConfigure.ok()) {
         ALOGE("Cannot read the old configuration from map: %s",
-              oldConfiguration.error().message().c_str());
-        return Status(oldConfiguration.error().code(), oldConfiguration.error().message());
+              oldConfigure.error().message().c_str());
+        return Status(oldConfigure.error().code(), oldConfigure.error().message());
     }
 
     // Write to the configuration map to inform the kernel eBPF program to switch
     // from using one map to the other. Use flag BPF_EXIST here since the map should
     // be already populated in initMaps.
-    uint8_t newConfigure = (oldConfiguration.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
+    uint32_t newConfigure = (oldConfigure.value() == SELECT_MAP_A) ? SELECT_MAP_B : SELECT_MAP_A;
     auto res = mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY, newConfigure,
                                             BPF_EXIST);
     if (!res.ok()) {
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index ad53cb8..c920398 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -36,6 +36,7 @@
 
 #include <netdutils/MockSyscalls.h>
 
+#define TEST_BPF_MAP
 #include "TrafficController.h"
 #include "bpf/BpfUtils.h"
 #include "NetdUpdatablePublic.h"
@@ -65,7 +66,7 @@
     BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
     BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
-    BpfMap<uint32_t, uint8_t> mFakeConfigurationMap;
+    BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
     BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
     BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
 
@@ -73,52 +74,42 @@
         std::lock_guard guard(mTc.mMutex);
         ASSERT_EQ(0, setrlimitForTest());
 
-        mFakeCookieTagMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint64_t), sizeof(UidTagValue),
-                                          TEST_MAP_SIZE, 0));
+        mFakeCookieTagMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeCookieTagMap);
 
-        mFakeAppUidStatsMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(StatsValue),
-                                            TEST_MAP_SIZE, 0));
+        mFakeAppUidStatsMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeAppUidStatsMap);
 
-        mFakeStatsMapA.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(StatsKey), sizeof(StatsValue),
-                                       TEST_MAP_SIZE, 0));
+        mFakeStatsMapA.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeStatsMapA);
 
-        mFakeConfigurationMap.reset(
-                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), 1, 0));
+        mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_HASH, 1);
         ASSERT_VALID(mFakeConfigurationMap);
 
-        mFakeUidOwnerMap.reset(createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(UidOwnerValue),
-                                         TEST_MAP_SIZE, 0));
+        mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeUidOwnerMap);
-        mFakeUidPermissionMap.reset(
-                createMap(BPF_MAP_TYPE_HASH, sizeof(uint32_t), sizeof(uint8_t), TEST_MAP_SIZE, 0));
+        mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
         ASSERT_VALID(mFakeUidPermissionMap);
 
-        mTc.mCookieTagMap.reset(dupFd(mFakeCookieTagMap.getMap()));
+        mTc.mCookieTagMap = mFakeCookieTagMap;
         ASSERT_VALID(mTc.mCookieTagMap);
-        mTc.mAppUidStatsMap.reset(dupFd(mFakeAppUidStatsMap.getMap()));
+        mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
         ASSERT_VALID(mTc.mAppUidStatsMap);
-        mTc.mStatsMapA.reset(dupFd(mFakeStatsMapA.getMap()));
+        mTc.mStatsMapA = mFakeStatsMapA;
         ASSERT_VALID(mTc.mStatsMapA);
-        mTc.mConfigurationMap.reset(dupFd(mFakeConfigurationMap.getMap()));
+        mTc.mConfigurationMap = mFakeConfigurationMap;
         ASSERT_VALID(mTc.mConfigurationMap);
 
         // Always write to stats map A by default.
         ASSERT_RESULT_OK(mTc.mConfigurationMap.writeValue(CURRENT_STATS_MAP_CONFIGURATION_KEY,
                                                           SELECT_MAP_A, BPF_ANY));
-        mTc.mUidOwnerMap.reset(dupFd(mFakeUidOwnerMap.getMap()));
+        mTc.mUidOwnerMap = mFakeUidOwnerMap;
         ASSERT_VALID(mTc.mUidOwnerMap);
-        mTc.mUidPermissionMap.reset(dupFd(mFakeUidPermissionMap.getMap()));
+        mTc.mUidPermissionMap = mFakeUidPermissionMap;
         ASSERT_VALID(mTc.mUidPermissionMap);
         mTc.mPrivilegedUser.clear();
     }
 
-    int dupFd(const android::base::unique_fd& mapFd) {
-        return fcntl(mapFd.get(), F_DUPFD_CLOEXEC, 0);
-    }
-
     void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
         UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
         EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
@@ -308,6 +299,9 @@
     checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
     checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
     checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
+    checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH);
+    checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH);
+    checkUidOwnerRuleForChain(OEM_DENY_3, OEM_DENY_3_MATCH);
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(NONE, TEST_UID, ALLOW, ALLOWLIST));
     ASSERT_EQ(-EINVAL, mTc.changeUidOwnerRule(INVALID_CHAIN, TEST_UID, ALLOW, ALLOWLIST));
 }
@@ -319,6 +313,9 @@
     checkUidMapReplace("fw_powersave", uids, POWERSAVE_MATCH);
     checkUidMapReplace("fw_restricted", uids, RESTRICTED_MATCH);
     checkUidMapReplace("fw_low_power_standby", uids, LOW_POWER_STANDBY_MATCH);
+    checkUidMapReplace("fw_oem_deny_1", uids, OEM_DENY_1_MATCH);
+    checkUidMapReplace("fw_oem_deny_2", uids, OEM_DENY_2_MATCH);
+    checkUidMapReplace("fw_oem_deny_3", uids, OEM_DENY_3_MATCH);
     ASSERT_EQ(-EINVAL, mTc.replaceUidOwnerMap("unknow", true, uids));
 }
 
@@ -673,7 +670,7 @@
     BpfMap<uint64_t, UidTagValue> mCookieTagMap;
 
     void SetUp() {
-        mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+        mCookieTagMap.init(COOKIE_TAG_MAP_PATH);
         ASSERT_TRUE(mCookieTagMap.isValid());
     }
 
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index 847acec..2427aa9 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -36,6 +36,9 @@
     RESTRICTED = 4,
     LOW_POWER_STANDBY = 5,
     LOCKDOWN = 6,
+    OEM_DENY_1 = 7,
+    OEM_DENY_2 = 8,
+    OEM_DENY_3 = 9,
     INVALID_CHAIN
 };
 // LINT.ThenChange(packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java)
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 79e75ac..c019ce7 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -88,6 +88,9 @@
     static const char* LOCAL_POWERSAVE;
     static const char* LOCAL_RESTRICTED;
     static const char* LOCAL_LOW_POWER_STANDBY;
+    static const char* LOCAL_OEM_DENY_1;
+    static const char* LOCAL_OEM_DENY_2;
+    static const char* LOCAL_OEM_DENY_3;
 
   private:
     /*
@@ -149,13 +152,13 @@
      * the map right now:
      * - Entry with UID_RULES_CONFIGURATION_KEY:
      *    Store the configuration for the current uid rules. It indicates the device
-     *    is in doze/powersave/standby/restricted/low power standby mode.
+     *    is in doze/powersave/standby/restricted/low power standby/oem deny mode.
      * - Entry with CURRENT_STATS_MAP_CONFIGURATION_KEY:
      *    Stores the current live stats map that kernel program is writing to.
      *    Userspace can do scraping and cleaning job on the other one depending on the
      *    current configs.
      */
-    bpf::BpfMap<uint32_t, uint8_t> mConfigurationMap GUARDED_BY(mMutex);
+    bpf::BpfMap<uint32_t, uint32_t> mConfigurationMap GUARDED_BY(mMutex);
 
     /*
      * mUidOwnerMap: Store uids that are used for bandwidth control uid match.
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b535fa9..d0cb294 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -8349,7 +8349,15 @@
         mPendingIntentWakeLock.acquire();
         try {
             if (DBG) log("Sending " + pendingIntent);
-            pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            if (SdkLevel.isAtLeastT()) {
+                // Explicitly disallow the receiver from starting activities, to prevent apps from
+                // utilizing the PendingIntent as a backdoor to do this.
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            }
+            pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */,
+                    null /* requiredPermission */,
+                    SdkLevel.isAtLeastT() ? options.toBundle() : null);
         } catch (PendingIntent.CanceledException e) {
             if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
             mPendingIntentWakeLock.release();
@@ -11355,6 +11363,9 @@
         final int defaultRule;
         switch (chain) {
             case ConnectivityManager.FIREWALL_CHAIN_STANDBY:
+            case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+            case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+            case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3:
                 defaultRule = FIREWALL_RULE_ALLOW;
                 break;
             case ConnectivityManager.FIREWALL_CHAIN_DOZABLE:
@@ -11404,6 +11415,15 @@
                     mBpfNetMaps.replaceUidChain("fw_low_power_standby", true /* isAllowList */,
                             uids);
                     break;
+                case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1:
+                    mBpfNetMaps.replaceUidChain("fw_oem_deny_1", false /* isAllowList */, uids);
+                    break;
+                case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2:
+                    mBpfNetMaps.replaceUidChain("fw_oem_deny_2", false /* isAllowList */, uids);
+                    break;
+                case ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3:
+                    mBpfNetMaps.replaceUidChain("fw_oem_deny_3", false /* isAllowList */, uids);
+                    break;
                 default:
                     throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
                             + chain);
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 509e881..58731e0 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -140,6 +140,30 @@
     ],
 }
 
+// defaults for tests that need to build against framework-connectivity's @hide APIs, but also
+// using fully @hide classes that are jarjared (because they have no API member). Similar to
+// framework-connectivity-test-defaults above but uses pre-jarjar class names.
+// Only usable from targets that have visibility on framework-connectivity-pre-jarjar, and apply
+// connectivity jarjar rules so that references to jarjared classes still match: this is limited to
+// connectivity internal tests only.
+java_defaults {
+    name: "framework-connectivity-internal-test-defaults",
+    sdk_version: "core_platform", // tests can use @CorePlatformApi's
+    libs: [
+        // order matters: classes in framework-connectivity are resolved before framework,
+        // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+        // stubs in framework
+        "framework-connectivity-pre-jarjar",
+        "framework-connectivity-t-pre-jarjar",
+        "framework-tethering.impl",
+        "framework",
+
+        // if sdk_version="" this gets automatically included, but here we need to add manually.
+        "framework-res",
+    ],
+    defaults_visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+}
+
 // Defaults for tests that want to run in mainline-presubmit.
 // Not widely used because many of our tests have AndroidTest.xml files and
 // use the mainline-param config-descriptor metadata in AndroidTest.xml.
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index b66a979..581ee22 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -46,6 +46,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk31;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -1307,6 +1308,7 @@
     }
 
     @Test @IgnoreUpTo(SC_V2)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden on T or above")
     @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabled() {
         final LinkProperties lp = new LinkProperties();
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index e979a3b..a6ed762 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -62,6 +62,7 @@
     // sdk_version: "current",
     platform_apis: true,
     required: ["ConnectivityChecker"],
+    test_config_template: "AndroidTestTemplate.xml",
 }
 
 // Networking CTS tests for development and release. These tests always target the platform SDK
@@ -79,7 +80,16 @@
         "cts",
         "general-tests",
     ],
-    test_config_template: "AndroidTestTemplate.xml",
+}
+
+java_defaults {
+    name: "CtsNetTestCasesApiStableDefaults",
+    // TODO: CTS should not depend on the entirety of the networkstack code.
+    static_libs: [
+        "NetworkStackApiStableLib",
+    ],
+    jni_uses_sdk_apis: true,
+    min_sdk_version: "29",
 }
 
 // Networking CTS tests that target the latest released SDK. These tests can be installed on release
@@ -87,14 +97,11 @@
 // on release devices.
 android_test {
     name: "CtsNetTestCasesLatestSdk",
-    defaults: ["CtsNetTestCasesDefaults"],
-    // TODO: CTS should not depend on the entirety of the networkstack code.
-    static_libs: [
-        "NetworkStackApiStableLib",
+    defaults: [
+        "CtsNetTestCasesDefaults",
+        "CtsNetTestCasesApiStableDefaults",
     ],
-    jni_uses_sdk_apis: true,
-    min_sdk_version: "29",
-    target_sdk_version: "30",
+    target_sdk_version: "33",
     test_suites: [
         "general-tests",
         "mts-dnsresolver",
@@ -102,5 +109,21 @@
         "mts-tethering",
         "mts-wifi",
     ],
-    test_config_template: "AndroidTestTemplate.xml",
 }
+
+android_test {
+    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+    defaults: [
+        "CtsNetTestCasesDefaults",
+        "CtsNetTestCasesApiStableDefaults",
+    ],
+    target_sdk_version: "31",
+    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
+    instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-networking",
+    ],
+}
+
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 33f3af5..d2fb04a 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -33,10 +33,21 @@
     <target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer">
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.cts" />
+        <option name="package" value="{PACKAGE}" />
         <option name="runtime-hint" value="9m4s" />
         <option name="hidden-api-checks" value="false" />
         <option name="isolated-storage" value="false" />
+        <!-- Test filter that allows test APKs to select which tests they want to run by annotating
+             those tests with an annotation matching the name of the APK.
+
+             This allows us to maintain one AndroidTestTemplate.xml for all CtsNetTestCases*.apk,
+             and have CtsNetTestCases and CtsNetTestCasesLatestSdk run all tests, but have
+             CtsNetTestCasesMaxTargetSdk31 run only tests that require target SDK 31.
+
+             This relies on the fact that if the class specified in include-annotation exists, then
+             the runner will only run the tests annotated with that annotation, but if it does not,
+             the runner will run all the tests. -->
+        <option name="include-annotation" value="com.android.testutils.filters.{MODULE}" />
     </test>
     <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
         one of the Mainline modules below is present on the device used for testing. -->
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index bdda82a..c00bbf4 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -37,6 +37,11 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.EXTRA_NETWORK;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
 import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -195,6 +200,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -204,6 +210,8 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
 import java.net.HttpURLConnection;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -218,6 +226,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
@@ -255,6 +264,7 @@
     private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
     private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 5_000;
     private static final int NO_CALLBACK_TIMEOUT_MS = 100;
+    private static final int SOCKET_TIMEOUT_MS = 100;
     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
     private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
     // device could have only one interface: data, wifi.
@@ -3279,14 +3289,16 @@
             // TODD: Have a significant signal to know the uids has been sent to netd.
             assertBindSocketToNetworkSuccess(network);
 
-            // Uid is in allowed list. Try file network request again.
-            requestNetwork(restrictedRequest, restrictedNetworkCb);
-            // Verify that the network is restricted.
-            restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    NETWORK_CALLBACK_TIMEOUT_MS,
-                    entry -> network.equals(entry.getNetwork())
-                            && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
-                            .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+            if (TestUtils.shouldTestTApis()) {
+                // Uid is in allowed list. Try file network request again.
+                requestNetwork(restrictedRequest, restrictedNetworkCb);
+                // Verify that the network is restricted.
+                restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                        NETWORK_CALLBACK_TIMEOUT_MS,
+                        entry -> network.equals(entry.getNetwork())
+                                && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+                                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
+            }
         } finally {
             agent.unregister();
 
@@ -3316,6 +3328,113 @@
         assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
     }
 
+    private void checkFirewallBlocking(final DatagramSocket srcSock, final DatagramSocket dstSock,
+            final boolean expectBlock) throws Exception {
+        final Random random = new Random();
+        final byte[] sendData = new byte[100];
+        random.nextBytes(sendData);
+
+        final DatagramPacket pkt = new DatagramPacket(sendData, sendData.length,
+                InetAddresses.parseNumericAddress("::1"), dstSock.getLocalPort());
+        try {
+            srcSock.send(pkt);
+        } catch (IOException e) {
+            if (expectBlock) {
+                return;
+            }
+            fail("Expect not to be blocked by firewall but sending packet was blocked");
+        }
+
+        if (expectBlock) {
+            fail("Expect to be blocked by firewall but sending packet was not blocked");
+        }
+
+        dstSock.receive(pkt);
+        assertArrayEquals(sendData, pkt.getData());
+    }
+
+    private static final boolean EXPECT_PASS = false;
+    private static final boolean EXPECT_BLOCK = true;
+
+    private void doTestFirewallBlockingDenyRule(final int chain) {
+        runWithShellPermissionIdentity(() -> {
+            try (DatagramSocket srcSock = new DatagramSocket();
+                 DatagramSocket dstSock = new DatagramSocket()) {
+                dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+                // No global config, No uid config
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // Has global config, No uid config
+                mCm.setFirewallChainEnabled(chain, true /* enable */);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // Has global config, Has uid config
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+                // No global config, Has uid config
+                mCm.setFirewallChainEnabled(chain, false /* enable */);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // No global config, No uid config
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+            } finally {
+                mCm.setFirewallChainEnabled(chain, false /* enable */);
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+            }
+        }, NETWORK_SETTINGS);
+    }
+
+    private void doTestFirewallBlockingAllowRule(final int chain) {
+        runWithShellPermissionIdentity(() -> {
+            try (DatagramSocket srcSock = new DatagramSocket();
+                 DatagramSocket dstSock = new DatagramSocket()) {
+                dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+                // No global config, No uid config
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // Has global config, No uid config
+                mCm.setFirewallChainEnabled(chain, true /* enable */);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_BLOCK);
+
+                // Has global config, Has uid config
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_ALLOW);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // No global config, Has uid config
+                mCm.setFirewallChainEnabled(chain, false /* enable */);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+
+                // No global config, No uid config
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+            } finally {
+                mCm.setFirewallChainEnabled(chain, false /* enable */);
+                mCm.setUidFirewallRule(chain, Process.myUid(), FIREWALL_RULE_DENY);
+            }
+        }, NETWORK_SETTINGS);
+    }
+
+    @Ignore("TODO: temporarily ignore tests until prebuilts are updated")
+    @Test @IgnoreUpTo(SC_V2)
+    public void testFirewallBlocking() {
+        // Following tests affect the actual state of networking on the device after the test.
+        // This might cause unexpected behaviour of the device. So, we skip them for now.
+        // We will enable following tests after adding the logic of firewall state restoring.
+        // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_DOZABLE);
+        // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_POWERSAVE);
+        // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_RESTRICTED);
+        // doTestFirewallBlockingAllowRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+
+        // doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_STANDBY);
+        doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestFirewallBlockingDenyRule(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
     private void assumeTestSApis() {
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
         // shims, and @IgnoreUpTo does not check that.
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index bfc9b29..293da67 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -20,6 +20,16 @@
 import android.Manifest.permission.NETWORK_SETTINGS
 import android.content.Context
 import android.net.ConnectivityManager
+import android.net.EthernetManager
+import android.net.EthernetManager.InterfaceStateListener
+import android.net.EthernetManager.ROLE_CLIENT
+import android.net.EthernetManager.ROLE_NONE
+import android.net.EthernetManager.ROLE_SERVER
+import android.net.EthernetManager.STATE_ABSENT
+import android.net.EthernetManager.STATE_LINK_DOWN
+import android.net.EthernetManager.STATE_LINK_UP
+import android.net.EthernetManager.TetheredInterfaceCallback
+import android.net.EthernetManager.TetheredInterfaceRequest
 import android.net.EthernetNetworkSpecifier
 import android.net.InetAddresses
 import android.net.IpConfiguration
@@ -32,47 +42,47 @@
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Build
 import android.os.Handler
 import android.os.HandlerExecutor
 import android.os.Looper
+import android.os.SystemProperties
 import android.platform.test.annotations.AppModeFull
 import android.util.ArraySet
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
-import com.android.networkstack.apishim.EthernetManagerShimImpl
-import com.android.networkstack.apishim.common.EthernetManagerShim.InterfaceStateListener
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_ABSENT
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_DOWN
-import com.android.networkstack.apishim.common.EthernetManagerShim.STATE_LINK_UP
 import com.android.testutils.anyNetwork
 import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
 import com.android.testutils.RouterAdvertisementResponder
-import com.android.testutils.SC_V2
 import com.android.testutils.TapPacketReader
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.runAsShell
 import com.android.testutils.waitForIdle
 import org.junit.After
+import org.junit.Assume.assumeFalse
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.net.Inet6Address
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
 import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
 
-private const val TIMEOUT_MS = 1000L
+// TODO: try to lower this timeout in the future. Currently, ethernet tests are still flaky because
+// the interface is not ready fast enough (mostly due to the up / up / down / up issue).
+private const val TIMEOUT_MS = 2000L
 private const val NO_CALLBACK_TIMEOUT_MS = 200L
 private val DEFAULT_IP_CONFIGURATION = IpConfiguration(IpConfiguration.IpAssignment.DHCP,
     IpConfiguration.ProxySettings.NONE, null, null)
@@ -83,14 +93,13 @@
     .build()
 
 @AppModeFull(reason = "Instant apps can't access EthernetManager")
-@RunWith(AndroidJUnit4::class)
+// EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class EthernetManagerTest {
-    // EthernetManager is not updatable before T, so tests do not need to be backwards compatible
-    @get:Rule
-    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
 
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
-    private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
+    private val em by lazy { context.getSystemService(EthernetManager::class.java) }
     private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
 
     private val ifaceListener = EthernetStateListener()
@@ -98,6 +107,8 @@
     private val addedListeners = ArrayList<EthernetStateListener>()
     private val networkRequests = ArrayList<TestableNetworkCallback>()
 
+    private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
+
     private class EthernetTestInterface(
         context: Context,
         private val handler: Handler
@@ -162,11 +173,11 @@
         }
 
         fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
-            expectCallback(createChangeEvent(iface, state, role))
+            expectCallback(createChangeEvent(iface.interfaceName, state, role))
         }
 
-        fun createChangeEvent(iface: EthernetTestInterface, state: Int, role: Int) =
-                InterfaceStateChanged(iface.interfaceName, state, role,
+        fun createChangeEvent(iface: String, state: Int, role: Int) =
+                InterfaceStateChanged(iface, state, role,
                         if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null)
 
         fun pollForNextCallback(): CallbackEntry {
@@ -175,8 +186,12 @@
 
         fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
 
+        fun eventuallyExpect(interfaceName: String, state: Int, role: Int) {
+            assertNotNull(eventuallyExpect(createChangeEvent(interfaceName, state, role)))
+        }
+
         fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
-            assertNotNull(eventuallyExpect(createChangeEvent(iface, state, role)))
+            eventuallyExpect(iface.interfaceName, state, role)
         }
 
         fun assertNoCallback() {
@@ -185,6 +200,34 @@
         }
     }
 
+    private class TetheredInterfaceListener : TetheredInterfaceCallback {
+        private val available = CompletableFuture<String>()
+
+        override fun onAvailable(iface: String) {
+            available.complete(iface)
+        }
+
+        override fun onUnavailable() {
+            available.completeExceptionally(IllegalStateException("onUnavailable was called"))
+        }
+
+        fun expectOnAvailable(): String {
+            return available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+        }
+
+        fun expectOnUnavailable() {
+            // Assert that the future fails with the IllegalStateException from the
+            // completeExceptionally() call inside onUnavailable.
+            assertFailsWith(IllegalStateException::class) {
+                try {
+                    available.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+                } catch (e: ExecutionException) {
+                    throw e.cause!!
+                }
+            }
+        }
+    }
+
     @Before
     fun setUp() {
         setIncludeTestInterfaces(true)
@@ -202,6 +245,7 @@
             em.removeInterfaceStateListener(listener)
         }
         networkRequests.forEach { cm.unregisterNetworkCallback(it) }
+        releaseTetheredInterface()
     }
 
     private fun addInterfaceStateListener(listener: EthernetStateListener) {
@@ -248,6 +292,19 @@
         networkRequests.remove(cb)
     }
 
+    private fun requestTetheredInterface() = TetheredInterfaceListener().also {
+        tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
+            em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it)
+        }
+    }
+
+    private fun releaseTetheredInterface() {
+        runAsShell(NETWORK_SETTINGS) {
+            tetheredInterfaceRequest?.release()
+            tetheredInterfaceRequest = null
+        }
+    }
+
     private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
         NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
             .setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
@@ -301,6 +358,35 @@
         }
     }
 
+    // TODO: this function is now used in two places (EthernetManagerTest and
+    // EthernetTetheringTest), so it should be moved to testutils.
+    private fun isAdbOverNetwork(): Boolean {
+        // If adb TCP port opened, this test may running by adb over network.
+        return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1 ||
+                SystemProperties.getInt("service.adb.tcp.port", -1) > -1)
+    }
+
+    @Ignore("TODO: temporarily ignore tests until prebuilts are updated")
+    @Test
+    fun testCallbacks_forServerModeInterfaces() {
+        // do not run this test when adb might be connected over ethernet.
+        assumeFalse(isAdbOverNetwork())
+
+        val listener = EthernetStateListener()
+        addInterfaceStateListener(listener)
+
+        // it is possible that a physical interface is present, so it is not guaranteed that iface
+        // will be put into server mode. This should not matter for the test though. Calling
+        // createInterface() makes sure we have at least one interface available.
+        val iface = createInterface()
+        val cb = requestTetheredInterface()
+        val ifaceName = cb.expectOnAvailable()
+        listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_SERVER)
+
+        releaseTetheredInterface()
+        listener.eventuallyExpect(ifaceName, STATE_LINK_UP, ROLE_CLIENT)
+    }
+
     /**
      * Validate all interfaces are returned for an EthernetStateListener upon registration.
      */
@@ -316,7 +402,10 @@
             assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
             // If the event's iface was created in the test, additional criteria can be validated.
             createdIfaces.find { it.interfaceName.equals(iface) }?.let {
-                assertEquals(event, listener.createChangeEvent(it, STATE_LINK_UP, ROLE_CLIENT))
+                assertEquals(event,
+                    listener.createChangeEvent(it.interfaceName,
+                                                        STATE_LINK_UP,
+                                                        ROLE_CLIENT))
             }
         }
         // Assert all callbacks are accounted for.
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 7286bf6..2b1d173 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -22,6 +22,7 @@
 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.TestableNetworkCallbackKt.anyNetwork;
 
@@ -60,12 +61,7 @@
 
 import com.android.internal.util.HexDump;
 import com.android.networkstack.apishim.ConstantsShim;
-import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
-import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
 import com.android.networkstack.apishim.VpnManagerShimImpl;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
-import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
-import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.apishim.common.VpnManagerShim;
 import com.android.networkstack.apishim.common.VpnProfileStateShim;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -228,22 +224,17 @@
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileCommon(
-            @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
+            @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
             boolean requiresValidation) throws Exception {
 
-        builderShim.setBypassable(true)
+        builder.setBypassable(true)
                 .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
                 .setProxy(TEST_PROXY_INFO)
                 .setMaxMtu(TEST_MTU)
                 .setMetered(false);
         if (TestUtils.shouldTestTApis()) {
-            builderShim.setRequiresInternetValidation(requiresValidation);
+            builder.setRequiresInternetValidation(requiresValidation);
         }
-
-        // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
-        // method and does not defined in shims.
-        // TODO: replace it in alternative way to remove the hidden method usage
-        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -259,14 +250,13 @@
                         ? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
                         IkeSessionTestUtils.CHILD_PARAMS);
 
-        final Ikev2VpnProfileBuilderShim builderShim =
-                Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, params)
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(params)
                         .setRequiresInternetValidation(requiresValidation)
                         .setProxy(TEST_PROXY_INFO)
                         .setMaxMtu(TEST_MTU)
                         .setMetered(false);
 
-        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -275,9 +265,8 @@
 
     private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
             boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
-        final Ikev2VpnProfileBuilderShim builder =
-                Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY, null)
-                        .setAuthPsk(TEST_PSK);
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
                 requiresValidation);
     }
@@ -285,8 +274,8 @@
     private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
             throws Exception {
 
-        final Ikev2VpnProfileBuilderShim builder =
-                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
                 false /* requiresValidation */);
@@ -294,8 +283,8 @@
 
     private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
             throws Exception {
-        final Ikev2VpnProfileBuilderShim builder =
-                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY, null)
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthDigitalSignature(
                                 mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
@@ -328,15 +317,8 @@
         assertNull(profile.getServerRootCaCert());
         assertNull(profile.getRsaPrivateKey());
         assertNull(profile.getUserCert());
-        final Ikev2VpnProfileShim<Ikev2VpnProfile> shim = new Ikev2VpnProfileShimImpl(profile);
-        if (TestUtils.shouldTestTApis()) {
-            assertEquals(requiresValidation, shim.isInternetValidationRequired());
-        } else {
-            try {
-                shim.isInternetValidationRequired();
-                fail("Only supported from API level 33");
-            } catch (UnsupportedApiLevelException expected) {
-            }
+        if (isAtLeastT()) {
+            assertEquals(requiresValidation, profile.isInternetValidationRequired());
         }
     }
 
@@ -348,8 +330,8 @@
 
         final IkeTunnelConnectionParams expectedParams = new IkeTunnelConnectionParams(
                 IkeSessionTestUtils.IKE_PARAMS_V6, IkeSessionTestUtils.CHILD_PARAMS);
-        final Ikev2VpnProfileBuilderShim ikeProfileBuilder =
-                Ikev2VpnProfileBuilderShimImpl.newInstance(null, null, expectedParams);
+        final Ikev2VpnProfile.Builder ikeProfileBuilder =
+                new Ikev2VpnProfile.Builder(expectedParams);
         // Verify the other Ike options could not be set with IkeTunnelConnectionParams.
         final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
         assertThrows(expected, () -> ikeProfileBuilder.setAuthPsk(TEST_PSK));
@@ -358,10 +340,9 @@
         assertThrows(expected, () -> ikeProfileBuilder.setAuthDigitalSignature(
                 mUserCertKey.cert, mUserCertKey.key, mServerRootCa));
 
-        final Ikev2VpnProfile profile = (Ikev2VpnProfile) ikeProfileBuilder.build().getProfile();
+        final Ikev2VpnProfile profile = ikeProfileBuilder.build();
 
-        assertEquals(expectedParams,
-                new Ikev2VpnProfileShimImpl(profile).getIkeTunnelConnectionParams());
+        assertEquals(expectedParams, profile.getIkeTunnelConnectionParams());
     }
 
     @Test
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 33a0a83..69ec189 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -22,6 +22,7 @@
 import android.net.Network
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkRequest
@@ -45,7 +46,9 @@
 import android.net.nsd.NsdManager.RegistrationListener
 import android.net.nsd.NsdManager.ResolveListener
 import android.net.nsd.NsdServiceInfo
+import android.os.Handler
 import android.os.HandlerThread
+import android.os.Process.myTid
 import android.platform.test.annotations.AppModeFull
 import android.util.Log
 import androidx.test.platform.app.InstrumentationRegistry
@@ -62,6 +65,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.net.ServerSocket
@@ -111,12 +115,20 @@
 
     private interface NsdEvent
     private open class NsdRecord<T : NsdEvent> private constructor(
-        private val history: ArrayTrackRecord<T>
+        private val history: ArrayTrackRecord<T>,
+        private val expectedThreadId: Int? = null
     ) : TrackRecord<T> by history {
-        constructor() : this(ArrayTrackRecord())
+        constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
 
         val nextEvents = history.newReadHead()
 
+        override fun add(e: T): Boolean {
+            if (expectedThreadId != null) {
+                assertEquals(expectedThreadId, myTid(), "Callback is running on the wrong thread")
+            }
+            return history.add(e)
+        }
+
         inline fun <reified V : NsdEvent> expectCallbackEventually(
             crossinline predicate: (V) -> Boolean = { true }
         ): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
@@ -136,8 +148,8 @@
         }
     }
 
-    private class NsdRegistrationRecord : RegistrationListener,
-            NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
+    private class NsdRegistrationRecord(expectedThreadId: Int? = null) : RegistrationListener,
+            NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
         sealed class RegistrationEvent : NsdEvent {
             abstract val serviceInfo: NsdServiceInfo
 
@@ -174,8 +186,8 @@
         }
     }
 
-    private class NsdDiscoveryRecord : DiscoveryListener,
-            NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
+    private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
+            DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
         sealed class DiscoveryEvent : NsdEvent {
             data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
                 : DiscoveryEvent()
@@ -452,7 +464,7 @@
         }
     }
 
-    @Test
+    @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
     fun testNsdManager_DiscoverWithNetworkRequest() {
         // This test requires shims supporting T+ APIs (discovering on network request)
         assumeTrue(TestUtils.shouldTestTApis())
@@ -462,9 +474,12 @@
         si.serviceName = this.serviceName
         si.port = 12345 // Test won't try to connect so port does not matter
 
-        val registrationRecord = NsdRegistrationRecord()
-        val registeredInfo1 = registerService(registrationRecord, si)
-        val discoveryRecord = NsdDiscoveryRecord()
+        val handler = Handler(handlerThread.looper)
+        val executor = Executor { handler.post(it) }
+
+        val registrationRecord = NsdRegistrationRecord(expectedThreadId = handlerThread.threadId)
+        val registeredInfo1 = registerService(registrationRecord, si, executor)
+        val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
 
         tryTest {
             val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
@@ -474,7 +489,7 @@
                             .addTransportType(TRANSPORT_TEST)
                             .setNetworkSpecifier(specifier)
                             .build(),
-                    Executor { it.run() }, discoveryRecord)
+                    executor, discoveryRecord)
 
             val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
             assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
@@ -490,7 +505,7 @@
             assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
 
             registrationRecord.expectCallback<ServiceUnregistered>()
-            val registeredInfo2 = registerService(registrationRecord, si)
+            val registeredInfo2 = registerService(registrationRecord, si, executor)
             val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
             assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
@@ -517,6 +532,39 @@
         }
     }
 
+    @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
+    fun testNsdManager_DiscoverWithNetworkRequest_NoMatchingNetwork() {
+        // This test requires shims supporting T+ APIs (discovering on network request)
+        assumeTrue(TestUtils.shouldTestTApis())
+
+        val si = NsdServiceInfo()
+        si.serviceType = SERVICE_TYPE
+        si.serviceName = this.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val handler = Handler(handlerThread.looper)
+        val executor = Executor { handler.post(it) }
+
+        val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
+        val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+
+        tryTest {
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                    NetworkRequest.Builder()
+                            .removeCapability(NET_CAPABILITY_TRUSTED)
+                            .addTransportType(TRANSPORT_TEST)
+                            // Specified network does not have this capability
+                            .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+                            .setNetworkSpecifier(specifier)
+                            .build(),
+                    executor, discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStarted>()
+        } cleanup {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        }
+    }
+
     @Test
     fun testNsdManager_ResolveOnNetwork() {
         // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
@@ -648,9 +696,12 @@
     /**
      * Register a service and return its registration record.
      */
-    private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
-        nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, Executor { it.run() },
-                record)
+    private fun registerService(
+        record: NsdRegistrationRecord,
+        si: NsdServiceInfo,
+        executor: Executor = Executor { it.run() }
+    ): NsdServiceInfo {
+        nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, executor, record)
         // We may not always get the name that we tried to register;
         // This events tells us the name that was registered.
         val cb = record.expectCallback<ServiceRegistered>()
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index e9c4e5a..6096a8b 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -56,7 +56,7 @@
     defaults: ["CtsTetheringTestDefaults"],
 
     min_sdk_version: "30",
-    target_sdk_version: "30",
+    target_sdk_version: "33",
 
     static_libs: [
         "TetheringIntegrationTestsLatestSdkLib",
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index c9a41ba..18ace4e 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -112,7 +112,7 @@
     name: "FrameworksNetTestsDefaults",
     min_sdk_version: "30",
     defaults: [
-        "framework-connectivity-test-defaults",
+        "framework-connectivity-internal-test-defaults",
     ],
     srcs: [
         "java/**/*.java",
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index e5e7ebc..829b824 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -42,7 +42,9 @@
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+// TODO(b/234099453): re-enable once a newer prebuilt is available
+// @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.CUR_DEVELOPMENT)
 public class NsdServiceInfoTest {
 
     public final static InetAddress LOCALHOST;
@@ -125,6 +127,7 @@
         fullInfo.setPort(4242);
         fullInfo.setHost(LOCALHOST);
         fullInfo.setNetwork(new Network(123));
+        fullInfo.setInterfaceIndex(456);
         checkParcelable(fullInfo);
 
         NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -175,6 +178,7 @@
         assertEquals(original.getHost(), result.getHost());
         assertTrue(original.getPort() == result.getPort());
         assertEquals(original.getNetwork(), result.getNetwork());
+        assertEquals(original.getInterfaceIndex(), result.getInterfaceIndex());
 
         // Assert equality of attribute map.
         Map<String, byte[]> originalMap = original.getAttributes();
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 44550e6..b9a18ab 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -52,8 +52,17 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
+import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
@@ -9547,6 +9556,98 @@
         verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
     }
 
+    private void doTestSetUidFirewallRule(final int chain, final int defaultRule) {
+        final int uid = 1001;
+        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
+        verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_ALLOW);
+        reset(mBpfNetMaps);
+
+        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DENY);
+        verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_DENY);
+        reset(mBpfNetMaps);
+
+        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
+        verify(mBpfNetMaps).setUidRule(chain, uid, defaultRule);
+        reset(mBpfNetMaps);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSetUidFirewallRule() throws Exception {
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
+        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testSetFirewallChainEnabled() throws Exception {
+        final List<Integer> firewallChains = Arrays.asList(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED,
+                FIREWALL_CHAIN_LOW_POWER_STANDBY,
+                FIREWALL_CHAIN_OEM_DENY_1,
+                FIREWALL_CHAIN_OEM_DENY_2,
+                FIREWALL_CHAIN_OEM_DENY_3);
+        for (final int chain: firewallChains) {
+            mCm.setFirewallChainEnabled(chain, true /* enabled */);
+            verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
+            reset(mBpfNetMaps);
+
+            mCm.setFirewallChainEnabled(chain, false /* enabled */);
+            verify(mBpfNetMaps).setChildChain(chain, false /* enable */);
+            reset(mBpfNetMaps);
+        }
+    }
+
+    private void doTestReplaceFirewallChain(final int chain, final String chainName,
+            final boolean allowList) {
+        final int[] uids = new int[] {1001, 1002};
+        mCm.replaceFirewallChain(chain, uids);
+        verify(mBpfNetMaps).replaceUidChain(chainName, allowList, uids);
+        reset(mBpfNetMaps);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testReplaceFirewallChain() {
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_DOZABLE, "fw_dozable", true);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_STANDBY, "fw_standby", false);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE, "fw_powersave",  true);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED, "fw_restricted", true);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY, "fw_low_power_standby", true);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1, "fw_oem_deny_1", false);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2, "fw_oem_deny_2", false);
+        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3, "fw_oem_deny_3", false);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testInvalidFirewallChain() throws Exception {
+        final int uid = 1001;
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected,
+                () -> mCm.setUidFirewallRule(-1 /* chain */, uid, FIREWALL_RULE_ALLOW));
+        assertThrows(expected,
+                () -> mCm.setUidFirewallRule(100 /* chain */, uid, FIREWALL_RULE_ALLOW));
+        assertThrows(expected, () -> mCm.replaceFirewallChain(-1 /* chain */, new int[]{uid}));
+        assertThrows(expected, () -> mCm.replaceFirewallChain(100 /* chain */, new int[]{uid}));
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testInvalidFirewallRule() throws Exception {
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected,
+                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+                        1001 /* uid */, -1 /* rule */));
+        assertThrows(expected,
+                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
+                        1001 /* uid */, 100 /* rule */));
+    }
+
     /**
      * Test mutable and requestable network capabilities such as
      * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 3c228d0..d3cfb76 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -19,7 +19,9 @@
 import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.any;
@@ -36,6 +38,12 @@
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.mdns.aidl.DiscoveryInfo;
+import android.net.mdns.aidl.GetAddressInfo;
+import android.net.mdns.aidl.IMDnsEventListener;
+import android.net.mdns.aidl.ResolutionInfo;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
 import android.net.nsd.MDnsManager;
@@ -63,6 +71,7 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.AdditionalAnswers;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -74,7 +83,9 @@
 //  - test NSD_ON ENABLE/DISABLED listening
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+// TODO(b/234099453): re-enable once a newer prebuilt is available
+// @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.CUR_DEVELOPMENT)
 public class NsdServiceTest {
 
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
@@ -275,6 +286,105 @@
         verify(mMockMDnsM, never()).stopDaemon();
     }
 
+    @Test
+    public void testDiscoverOnTetheringDownstream() throws Exception {
+        NsdService service = makeService();
+        NsdManager client = connectClient(service);
+
+        final String serviceType = "a_type";
+        final String serviceName = "a_name";
+        final String domainName = "mytestdevice.local";
+        final int interfaceIdx = 123;
+        final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
+        client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
+        waitForIdle();
+
+        final ArgumentCaptor<IMDnsEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(IMDnsEventListener.class);
+        verify(mMockMDnsM).registerEventListener(listenerCaptor.capture());
+        final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType),
+                eq(0) /* interfaceIdx */);
+        // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+        // this needs to use a timeout
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType);
+
+        final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+                discIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_FOUND,
+                serviceName,
+                serviceType,
+                domainName,
+                interfaceIdx,
+                INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
+        final IMDnsEventListener eventListener = listenerCaptor.getValue();
+        eventListener.onServiceDiscoveryStatus(discoveryInfo);
+        waitForIdle();
+
+        final ArgumentCaptor<NsdServiceInfo> discoveredInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture());
+        final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue();
+        assertEquals(serviceName, foundInfo.getServiceName());
+        assertEquals(serviceType, foundInfo.getServiceType());
+        assertNull(foundInfo.getHost());
+        assertNull(foundInfo.getNetwork());
+        assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
+
+        // After discovering the service, verify resolving it
+        final NsdManager.ResolveListener resolveListener = mock(NsdManager.ResolveListener.class);
+        client.resolveService(foundInfo, resolveListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
+                eq("local.") /* domain */, eq(interfaceIdx));
+
+        final int servicePort = 10123;
+        final String serviceFullName = serviceName + "." + serviceType;
+        final ResolutionInfo resolutionInfo = new ResolutionInfo(
+                resolvIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_RESOLVED,
+                null /* serviceName */,
+                null /* serviceType */,
+                null /* domain */,
+                serviceFullName,
+                domainName,
+                servicePort,
+                new byte[0] /* txtRecord */,
+                interfaceIdx);
+
+        doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
+        eventListener.onServiceResolutionStatus(resolutionInfo);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
+                eq(interfaceIdx));
+
+        final String serviceAddress = "192.0.2.123";
+        final GetAddressInfo addressInfo = new GetAddressInfo(
+                getAddrIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
+                serviceFullName,
+                serviceAddress,
+                interfaceIdx,
+                INetd.LOCAL_NET_ID);
+        eventListener.onGettingServiceAddressStatus(addressInfo);
+        waitForIdle();
+
+        final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
+        final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
+        assertEquals(serviceName, resolvedService.getServiceName());
+        assertEquals("." + serviceType, resolvedService.getServiceType());
+        assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
+        assertEquals(servicePort, resolvedService.getPort());
+        assertNull(resolvedService.getNetwork());
+        assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index ec51537..d1bf40e 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -27,6 +27,7 @@
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -184,7 +185,7 @@
                 (int) setting);
     }
 
-    private void testGetMultipathPreference(
+    private void prepareGetMultipathPreferenceTest(
             long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
             long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
 
@@ -286,7 +287,7 @@
 
     @Test
     public void testGetMultipathPreference_SubscriptionQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
                 DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
@@ -301,7 +302,7 @@
 
     @Test
     public void testGetMultipathPreference_UserWarningQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 // Remaining days are 29 days from Apr. 2nd to May 1st.
@@ -320,7 +321,7 @@
 
     @Test
     public void testGetMultipathPreference_SnoozedWarningQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 POLICY_SNOOZED /* policyWarning */,
@@ -339,7 +340,7 @@
 
     @Test
     public void testGetMultipathPreference_SnoozedBothQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 // 29 days from Apr. 2nd to May 1st
@@ -356,7 +357,7 @@
 
     @Test
     public void testGetMultipathPreference_SettingChanged() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 WARNING_DISABLED,
@@ -381,7 +382,7 @@
 
     @Test
     public void testGetMultipathPreference_ResourceChanged() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 WARNING_DISABLED,
@@ -404,4 +405,45 @@
         verify(mStatsManager, times(1)).registerUsageCallback(
                 any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
     }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testOnThresholdReached() {
+        prepareGetMultipathPreferenceTest(
+                DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+                DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
+                DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
+                LIMIT_DISABLED,
+                DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+                2_500_000 /* defaultResSetting */,
+                false /* roaming */);
+
+        final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+        final ArgumentCaptor<NetworkTemplate> networkTemplateCaptor =
+                ArgumentCaptor.forClass(NetworkTemplate.class);
+        // Verify the callback is registered with quota - used = 14 - 2 = 12MB.
+        verify(mStatsManager, times(1)).registerUsageCallback(
+                networkTemplateCaptor.capture(), eq(DataUnit.MEGABYTES.toBytes(12)), any(),
+                usageCallbackCaptor.capture());
+
+        // Capture arguments for later use.
+        final NetworkStatsManager.UsageCallback usageCallback = usageCallbackCaptor.getValue();
+        final NetworkTemplate template = networkTemplateCaptor.getValue();
+        assertNotNull(usageCallback);
+        assertNotNull(template);
+
+        // Decrease quota from 14 to 11, and trigger the event.
+        // TODO: Mock daily and monthly used bytes instead of changing subscription to simulate
+        //  remaining quota changed.
+        when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
+                .thenReturn(DataUnit.MEGABYTES.toBytes(11));
+        usageCallback.onThresholdReached(template);
+
+        // Callback must have been re-registered with new remaining quota = 11 - 2 = 9MB.
+        verify(mStatsManager, times(1))
+                .unregisterUsageCallback(eq(usageCallback));
+        verify(mStatsManager, times(1)).registerUsageCallback(
+                eq(template), eq(DataUnit.MEGABYTES.toBytes(9)), any(), eq(usageCallback));
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index bae0433..eb35469 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -45,6 +45,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -100,13 +101,18 @@
 import android.net.VpnService;
 import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
+import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.ipsec.ike.exceptions.IkeTimeoutException;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
+import android.os.PowerWhitelistManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -125,6 +131,7 @@
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.HexDump;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.server.DeviceIdleInternal;
 import com.android.server.IpSecService;
 import com.android.server.vcn.util.PersistableBundleUtils;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -149,6 +156,7 @@
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -230,6 +238,7 @@
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private IpSecService mIpSecService;
     @Mock private VpnProfileStore mVpnProfileStore;
+    @Mock DeviceIdleInternal mDeviceIdleInternal;
     private final VpnProfile mVpnProfile;
 
     private IpSecManager mIpSecManager;
@@ -403,6 +412,12 @@
                 disallow);
     }
 
+    private void verifyPowerSaveTempWhitelistApp(String packageName) {
+        verify(mDeviceIdleInternal).addPowerSaveTempWhitelistApp(anyInt(), eq(packageName),
+                anyLong(), anyInt(), eq(false), eq(PowerWhitelistManager.REASON_VPN),
+                eq("VpnManager event"));
+    }
+
     @Test
     public void testGetAlwaysAndOnGetLockDown() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
@@ -1139,6 +1154,8 @@
         verifyPlatformVpnIsActivated(TEST_VPN_PKG);
         vpn.stopVpnProfile(TEST_VPN_PKG);
         verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
         // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
         // errorCode won't be set.
         verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
@@ -1150,6 +1167,8 @@
         verifyPlatformVpnIsActivated(TEST_VPN_PKG);
         vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
         verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
         // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
         // errorCode won't be set.
         verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
@@ -1165,6 +1184,8 @@
         // Enable VPN always-on for PKGS[1].
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(null /* sessionKey */,
                 VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
                 -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1173,6 +1194,8 @@
         // Enable VPN lockdown for PKGS[1].
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(null /* sessionKey */,
                 VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
                 -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1181,6 +1204,8 @@
         // Disable VPN lockdown for PKGS[1].
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(null /* sessionKey */,
                 VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
                 -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1189,6 +1214,8 @@
         // Disable VPN always-on.
         assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(null /* sessionKey */,
                 VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
                 -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1197,6 +1224,8 @@
         // Enable VPN always-on for PKGS[1] again.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[1]);
+        reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(null /* sessionKey */,
                 VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
                 -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
@@ -1205,6 +1234,8 @@
         // Enable VPN always-on for PKGS[2].
         assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
                 null /* lockdownAllowlist */));
+        verifyPowerSaveTempWhitelistApp(PKGS[2]);
+        reset(mDeviceIdleInternal);
         // PKGS[1] is replaced with PKGS[2].
         // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
         // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
@@ -1284,15 +1315,16 @@
                 config -> Arrays.asList(config.flags).contains(flag)));
     }
 
-    @Test
-    public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+    private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+            String category, int errorType, int errorCode) throws Exception {
         final ArgumentCaptor<IkeSessionCallback> captor =
                 ArgumentCaptor.forClass(IkeSessionCallback.class);
-        final IkeProtocolException exception = mock(IkeProtocolException.class);
-        when(exception.getErrorType())
-                .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
 
-        final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile));
+        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
         final NetworkCallback cb = triggerOnAvailableAndGetCallback();
 
         verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
@@ -1302,10 +1334,77 @@
         verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
                 .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
         final IkeSessionCallback ikeCb = captor.getValue();
-        ikeCb.onClosedExceptionally(exception);
+        ikeCb.onClosedWithException(exception);
 
-        verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
-        assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
+        verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
+        reset(mDeviceIdleInternal);
+        verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
+        if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+            verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+                    .unregisterNetworkCallback(eq(cb));
+        }
+    }
+
+    @Test
+    public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+        final IkeProtocolException exception = mock(IkeProtocolException.class);
+        final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+        when(exception.getErrorType()).thenReturn(errorCode);
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
+        final IkeProtocolException exception = mock(IkeProtocolException.class);
+        final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+        when(exception.getErrorType()).thenReturn(errorCode);
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final UnknownHostException unknownHostException = new UnknownHostException();
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+        when(exception.getCause()).thenReturn(unknownHostException);
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final IkeTimeoutException ikeTimeoutException =
+                new IkeTimeoutException("IkeTimeoutException");
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+        when(exception.getCause()).thenReturn(ikeTimeoutException);
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
+        final IkeNetworkLostException exception = new IkeNetworkLostException(
+                new Network(100));
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                VpnManager.ERROR_CODE_NETWORK_LOST);
+    }
+
+    @Test
+    public void testStartPlatformVpnFailedWithIOException() throws Exception {
+        final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+        final IOException ioException = new IOException();
+        final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+        when(exception.getCause()).thenReturn(ioException);
+        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+                VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+                errorCode);
     }
 
     @Test
@@ -1461,7 +1560,7 @@
         }
     }
 
-    private static final class TestDeps extends Vpn.Dependencies {
+    private final class TestDeps extends Vpn.Dependencies {
         public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
         public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
         public final File mStateFile;
@@ -1590,6 +1689,11 @@
 
         @Override
         public void setBlocking(FileDescriptor fd, boolean blocking) {}
+
+        @Override
+        public DeviceIdleInternal getDeviceIdleInternal() {
+            return mDeviceIdleInternal;
+        }
     }
 
     /**
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 115f0e1..e90d55d 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -68,6 +69,7 @@
 
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -445,7 +447,20 @@
         when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
         when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
         doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
-        doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
+
+        final AtomicBoolean ifaceUp = new AtomicBoolean(true);
+        doAnswer(inv -> ifaceUp.get()).when(mFactory).hasInterface(testIface);
+        doAnswer(inv ->
+                ifaceUp.get() ? EthernetManager.STATE_LINK_UP : EthernetManager.STATE_ABSENT)
+                .when(mFactory).getInterfaceState(testIface);
+        doAnswer(inv -> {
+            ifaceUp.set(true);
+            return null;
+        }).when(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
+        doAnswer(inv -> {
+            ifaceUp.set(false);
+            return null;
+        }).when(mFactory).removeInterface(testIface);
 
         final EthernetStateListener listener = spy(new EthernetStateListener());
         tracker.addListener(listener, true /* canUseRestrictedNetworks */);
@@ -456,7 +471,6 @@
         verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
         reset(listener);
 
-        doReturn(EthernetManager.STATE_ABSENT).when(mFactory).getInterfaceState(eq(testIface));
         tracker.setEthernetEnabled(false);
         waitForIdle();
         verify(mFactory).removeInterface(eq(testIface));
@@ -465,7 +479,6 @@
                 anyInt(), any());
         reset(listener);
 
-        doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
         tracker.setEthernetEnabled(true);
         waitForIdle();
         verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index d37ae23..b1d44ea 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -148,7 +148,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.time.Clock;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -1113,6 +1116,40 @@
     }
 
     @Test
+    public void testGetLatestSummary() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
+
+        // Increase arbitrary time which does not align to the bucket edge, create some traffic.
+        incrementCurrentTime(1751000L);
+        NetworkStats.Entry entry = new NetworkStats.Entry(
+                TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 50L, 5L, 51L, 1L, 3L);
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1).insertEntry(entry));
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        forcePollAndWaitForIdle();
+
+        // Verify the mocked stats is returned by querying with the range of the latest bucket.
+        final ZonedDateTime end =
+                ZonedDateTime.ofInstant(mClock.instant(), ZoneId.systemDefault());
+        final ZonedDateTime start = end.truncatedTo(ChronoUnit.HOURS);
+        NetworkStats stats = mSession.getSummaryForNetwork(buildTemplateWifi(TEST_WIFI_NETWORK_KEY),
+                start.toInstant().toEpochMilli(), end.toInstant().toEpochMilli());
+        assertEquals(1, stats.size());
+        assertValues(stats, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 50L, 5L, 51L, 1L, 3L);
+
+        // For getHistoryIntervalForNetwork, only includes buckets that atomically occur in
+        // the inclusive time range, instead of including the latest bucket. This behavior is
+        // already documented publicly, refer to {@link NetworkStatsManager#queryDetails}.
+    }
+
+    @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
     public void testUidStatsForTransport() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();
@@ -1143,9 +1180,12 @@
 
         assertEquals(3, stats.size());
         entry1.operations = 1;
+        entry1.iface = null;
         assertEquals(entry1, stats.getValues(0, null));
         entry2.operations = 1;
+        entry2.iface = null;
         assertEquals(entry2, stats.getValues(1, null));
+        entry3.iface = null;
         assertEquals(entry3, stats.getValues(2, null));
     }