Merge "Correct javadoc usage"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index bfba5cd..26eef96 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -31,7 +31,6 @@
         "apishim/**/*.java",
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
-        ":tethering-module-utils-srcs",
         ":services-tethering-shared-srcs",
         ":statslog-tethering-java-gen",
     ],
@@ -47,6 +46,7 @@
         "net-utils-framework-common",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
+        "net-utils-device-common-ip",
         "net-utils-device-common-netlink",
         "netd-client",
         "tetheringstatsprotos",
diff --git a/Tethering/apex/manifest.json b/Tethering/apex/manifest.json
index 3cb03ed..5d5ede6 100644
--- a/Tethering/apex/manifest.json
+++ b/Tethering/apex/manifest.json
@@ -1,4 +1,7 @@
 {
   "name": "com.android.tethering",
-  "version": 339990000
+
+  // Placeholder module version to be replaced during build.
+  // Do not change!
+  "version": 0
 }
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index b865a8e..18ef631 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -19,7 +19,6 @@
 import android.net.INetd;
 import android.net.MacAddress;
 import android.net.TetherStatsParcel;
-import android.net.util.SharedLog;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.SparseArray;
@@ -28,6 +27,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsValue;
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 0683e5e..fd9dab5 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -19,7 +19,6 @@
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 
 import android.net.MacAddress;
-import android.net.util.SharedLog;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -31,6 +30,7 @@
 
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.IBpfMap.ThrowingBiConsumer;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index 836761f..b4e3ba4 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -36,5 +36,4 @@
     void onTetherStatesChanged(in TetherStatesParcel states);
     void onTetherClientsChanged(in List<TetheredClient> clients);
     void onOffloadStatusChanged(int status);
-    void onSupportedTetheringTypes(long supportedBitmap);
 }
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index f33f846..253eacb 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -26,7 +26,7 @@
  * @hide
  */
 parcelable TetheringCallbackStartedParcel {
-    long supportedTypes;
+    boolean tetheringSupported;
     Network upstreamNetwork;
     TetheringConfigurationParcel config;
     TetherStatesParcel states;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index b3f0cf2..6f9b33e 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -183,12 +183,6 @@
      */
     public static final int TETHERING_WIGIG = 6;
 
-    /**
-     * The int value of last tethering type.
-     * @hide
-     */
-    public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG;
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
@@ -526,9 +520,6 @@
         }
 
         @Override
-        public void onSupportedTetheringTypes(long supportedBitmap) { }
-
-        @Override
         public void onUpstreamChanged(Network network) { }
 
         @Override
@@ -1042,29 +1033,15 @@
         /**
          * Called when tethering supported status changed.
          *
-         * <p>This callback will be called immediately after the callback is
-         * registered, and never be called if there is changes afterward.
-         *
-         * <p>Tethering may be disabled via system properties, device configuration, or device
-         * policy restrictions.
-         *
-         * @param supported whether any tethering type is supported.
-         */
-        default void onTetheringSupported(boolean supported) {}
-
-        /**
-         * Called when tethering supported status changed.
-         *
          * <p>This will be called immediately after the callback is registered, and may be called
          * multiple times later upon changes.
          *
          * <p>Tethering may be disabled via system properties, device configuration, or device
          * policy restrictions.
          *
-         * @param supportedTypes a set of @TetheringType which is supported.
-         * @hide
+         * @param supported The new supported status
          */
-        default void onSupportedTetheringTypes(@NonNull Set<Integer> supportedTypes) {}
+        default void onTetheringSupported(boolean supported) {}
 
         /**
          * Called when tethering upstream changed.
@@ -1362,8 +1339,7 @@
                 @Override
                 public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
                     executor.execute(() -> {
-                        callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes));
-                        callback.onTetheringSupported(parcel.supportedTypes != 0);
+                        callback.onTetheringSupported(parcel.tetheringSupported);
                         callback.onUpstreamChanged(parcel.upstreamNetwork);
                         sendErrorCallbacks(parcel.states);
                         sendRegexpsChanged(parcel.config);
@@ -1382,13 +1358,6 @@
                     });
                 }
 
-                @Override
-                public void onSupportedTetheringTypes(long supportedBitmap) {
-                    executor.execute(() -> {
-                        callback.onSupportedTetheringTypes(unpackBits(supportedBitmap));
-                    });
-                }
-
                 private void sendRegexpsChanged(TetheringConfigurationParcel parcel) {
                     callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps(
                             parcel.tetherableBluetoothRegexs,
@@ -1427,23 +1396,6 @@
     }
 
     /**
-     * Unpack bitmap to a set of bit position intergers.
-     * @hide
-     */
-    public static ArraySet<Integer> unpackBits(long val) {
-        final ArraySet<Integer> result = new ArraySet<>(Long.bitCount(val));
-        int bitPos = 0;
-        while (val != 0) {
-            if ((val & 1) == 1) result.add(bitPos);
-
-            val = val >>> 1;
-            bitPos++;
-        }
-
-        return result;
-    }
-
-    /**
      * Remove tethering event callback previously registered with
      * {@link #registerTetheringEventCallback}.
      *
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 437ed71..438b592 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -44,9 +44,7 @@
 import android.net.dhcp.DhcpServingParamsParcelExt;
 import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
-import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -64,6 +62,10 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.InterfaceController;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
 import com.android.networkstack.tethering.BpfCoordinator;
 import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ca4071f..49442a6 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -23,11 +23,11 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStats.UID_TETHERING;
-import static android.net.ip.ConntrackMonitor.ConntrackEvent;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.system.OsConstants.ETH_P_IP;
 import static android.system.OsConstants.ETH_P_IPV6;
 
+import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
 import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
 import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
@@ -40,11 +40,8 @@
 import android.net.NetworkStats;
 import android.net.NetworkStats.Entry;
 import android.net.TetherOffloadRuleParcel;
-import android.net.ip.ConntrackMonitor;
-import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
 import android.net.ip.IpServer;
 import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.system.ErrnoException;
@@ -65,12 +62,15 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.U32;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
 import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.ip.ConntrackMonitor;
+import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkSocket;
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 821b0e5..784ebd5 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -43,7 +43,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.net.util.SharedLog;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcel;
@@ -55,6 +54,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
 
 import java.io.PrintWriter;
 import java.util.BitSet;
diff --git a/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java b/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
index f3dcaa2..ab3929d 100644
--- a/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/IPv6TetheringCoordinator.java
@@ -24,9 +24,10 @@
 import android.net.RouteInfo;
 import android.net.ip.IpServer;
 import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
 import android.util.Log;
 
+import com.android.net.module.util.SharedLog;
+
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index d60c21d..94684af 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -43,7 +43,6 @@
 import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
 import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.provider.Settings;
 import android.system.ErrnoException;
@@ -53,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkSocket;
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 9da66d8..fbb342d 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -28,7 +28,6 @@
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
 import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
-import android.net.util.SharedLog;
 import android.net.util.SocketUtils;
 import android.os.Handler;
 import android.os.NativeHandle;
@@ -40,6 +39,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.NetlinkSocket;
 import com.android.net.module.util.netlink.StructNfGenMsg;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f613b73..89ed620 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -98,8 +98,6 @@
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringRequestParcel;
 import android.net.ip.IpServer;
-import android.net.shared.NetdUtils;
-import android.net.util.SharedLog;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
 import android.net.wifi.p2p.WifiP2pGroup;
@@ -136,6 +134,8 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
 import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
 import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
@@ -281,11 +281,6 @@
     private BluetoothPan mBluetoothPan;
     private PanServiceListener mBluetoothPanListener;
     private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
-    // AIDL doesn't support Set<Integer>. Maintain a int bitmap here. When the bitmap is passed to
-    // TetheringManager, TetheringManager would convert it to a set of Integer types.
-    // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be
-    // read from binder thread which called TetheringService directly.
-    private volatile long mSupportedTypeBitmap;
 
     public Tethering(TetheringDependencies deps) {
         mLog.mark("Tethering.constructed");
@@ -517,8 +512,6 @@
         mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically,
                 mConfig.isDunRequired);
         reportConfigurationChanged(mConfig.toStableParcelable());
-
-        updateSupportedDownstreams(mConfig);
     }
 
     private void maybeDunSettingChanged() {
@@ -1557,6 +1550,26 @@
         return mConfig;
     }
 
+    boolean hasAnySupportedDownstream() {
+        if ((mConfig.tetherableUsbRegexs.length != 0)
+                || (mConfig.tetherableWifiRegexs.length != 0)
+                || (mConfig.tetherableBluetoothRegexs.length != 0)) {
+            return true;
+        }
+
+        // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
+        // disabled (whole tethering settings would be hidden). This means tethering would also not
+        // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
+        // some devices in the field rely on this to disable tethering entirely.
+        if (!SdkLevel.isAtLeastT()) return false;
+
+        return (mConfig.tetherableWifiP2pRegexs.length != 0)
+                || (mConfig.tetherableNcmRegexs.length != 0)
+                || isEthernetSupported();
+    }
+
+    // TODO: using EtherentManager new API to check whether ethernet is supported when the API is
+    // ready to use.
     private boolean isEthernetSupported() {
         return mContext.getSystemService(Context.ETHERNET_SERVICE) != null;
     }
@@ -2346,7 +2359,7 @@
         mHandler.post(() -> {
             mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
             final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
-            parcel.supportedTypes = mSupportedTypeBitmap;
+            parcel.tetheringSupported = isTetheringSupported();
             parcel.upstreamNetwork = mTetherUpstream;
             parcel.config = mConfig.toStableParcelable();
             parcel.states =
@@ -2385,22 +2398,6 @@
         });
     }
 
-    private void reportTetheringSupportedChange(final long supportedBitmap) {
-        final int length = mTetheringEventCallbacks.beginBroadcast();
-        try {
-            for (int i = 0; i < length; i++) {
-                try {
-                    mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes(
-                            supportedBitmap);
-                } catch (RemoteException e) {
-                    // Not really very much to do here.
-                }
-            }
-        } finally {
-            mTetheringEventCallbacks.finishBroadcast();
-        }
-    }
-
     private void reportUpstreamChanged(UpstreamNetworkState ns) {
         final int length = mTetheringEventCallbacks.beginBroadcast();
         final Network network = (ns != null) ? ns.network : null;
@@ -2485,56 +2482,18 @@
         }
     }
 
-    private void updateSupportedDownstreams(final TetheringConfiguration config) {
-        final long preSupportedBitmap = mSupportedTypeBitmap;
-
-        if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) {
-            mSupportedTypeBitmap = 0;
-        } else {
-            mSupportedTypeBitmap = makeSupportedDownstreams(config);
-        }
-
-        if (preSupportedBitmap != mSupportedTypeBitmap) {
-            reportTetheringSupportedChange(mSupportedTypeBitmap);
-        }
-    }
-
-    private long makeSupportedDownstreams(final TetheringConfiguration config) {
-        long types = 0;
-        if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB);
-
-        if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI);
-
-        if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH);
-
-        // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are
-        // disabled (whole tethering settings would be hidden). This means tethering would also not
-        // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are
-        // some devices in the field rely on this to disable tethering entirely.
-        if (!SdkLevel.isAtLeastT() && types == 0) return types;
-
-        if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM);
-
-        if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P);
-
-        if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET);
-
-        return types;
-    }
-
     // if ro.tether.denied = true we default to no tethering
     // gservices could set the secure setting to 1 though to enable it on a build where it
     // had previously been turned off.
-    private boolean isTetheringAllowed() {
+    boolean isTetheringSupported() {
         final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1;
         final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
-        return tetherSupported
+        final boolean tetherEnabledInSettings = tetherSupported
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
-    }
 
-    boolean isTetheringSupported() {
-        return mSupportedTypeBitmap > 0;
+        return tetherEnabledInSettings && hasAnySupportedDownstream()
+                && !mEntitlementMgr.isProvisioningNeededButUnavailable();
     }
 
     private void dumpBpf(IndentingPrintWriter pw) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 7c36054..696a970 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -30,7 +30,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.TetheringConfigurationParcel;
-import android.net.util.SharedLog;
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -42,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SharedLog;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 8e0354d..611d1cf 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.net.INetd;
 import android.net.ip.IpServer;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -32,6 +31,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.BluetoothPanShimImpl;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
 import com.android.networkstack.tethering.metrics.TetheringMetrics;
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index f8dd673..16c031b 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -36,7 +36,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -46,6 +45,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
 import com.android.networkstack.apishim.common.ConnectivityManagerShim;
 import com.android.networkstack.tethering.util.PrefixUtils;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index ef4f052..86dca1c 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -192,13 +192,12 @@
         mUiAutomation.adoptShellPermissionIdentity(
                 MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE,
                 CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP);
+        mRunTests = mTm.isTetheringSupported() && mEm != null;
+        assumeTrue(mRunTests);
+
         mHandlerThread = new HandlerThread(getClass().getSimpleName());
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
-
-        mRunTests = isEthernetTetheringSupported();
-        assumeTrue(mRunTests);
-
         mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
     }
 
@@ -226,6 +225,7 @@
             mHandler.post(() -> reader.stop());
             mDownstreamReader = null;
         }
+        mHandlerThread.quitSafely();
         mTetheredInterfaceRequester.release();
         mEm.setIncludeTestInterfaces(false);
         maybeDeleteTestInterface();
@@ -236,7 +236,6 @@
         try {
             if (mRunTests) cleanUp();
         } finally {
-            mHandlerThread.quitSafely();
             mUiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -411,23 +410,6 @@
         // client, which is not possible in this test.
     }
 
-    private boolean isEthernetTetheringSupported() throws Exception {
-        final CompletableFuture<Boolean> future = new CompletableFuture<>();
-        final TetheringEventCallback callback = new TetheringEventCallback() {
-            @Override
-            public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
-                future.complete(supportedTypes.contains(TETHERING_ETHERNET));
-            }
-        };
-
-        try {
-            mTm.registerTetheringEventCallback(mHandler::post, callback);
-            return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } finally {
-            mTm.unregisterTetheringEventCallback(callback);
-        }
-    }
-
     private static final class MyTetheringEventCallback implements TetheringEventCallback {
         private final TetheringManager mTm;
         private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index 7ee69b2..d38a7c3 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -28,7 +28,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -38,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
 import org.junit.Before;
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index bf7e887..f242227 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -83,10 +83,7 @@
 import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
 import android.net.dhcp.IDhcpServerCallbacks;
-import android.net.ip.IpNeighborMonitor.NeighborEvent;
-import android.net.ip.IpNeighborMonitor.NeighborEventConsumer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
-import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -101,10 +98,15 @@
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
 import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.ip.ConntrackMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
+import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
 import com.android.networkstack.tethering.BpfCoordinator;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
 import com.android.networkstack.tethering.PrivateAddressCoordinator;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 3630f24..fa1d881 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -23,7 +23,6 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStats.UID_TETHERING;
-import static android.net.ip.ConntrackMonitor.ConntrackEvent;
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.system.OsConstants.ETH_P_IP;
 import static android.system.OsConstants.ETH_P_IPV6;
@@ -33,6 +32,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
 import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
 import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
 import static com.android.net.module.util.netlink.ConntrackMessage.Tuple;
@@ -82,10 +82,7 @@
 import android.net.NetworkStats;
 import android.net.TetherOffloadRuleParcel;
 import android.net.TetherStatsParcel;
-import android.net.ip.ConntrackMonitor;
-import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
 import android.net.ip.IpServer;
-import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.Handler;
 import android.os.test.TestLooper;
@@ -100,10 +97,13 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.NetworkStackConstants;
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
 import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.ip.ConntrackMonitor;
+import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkSocket;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 01d7b4b..e4263db 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -65,7 +65,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.util.SharedLog;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PersistableBundle;
@@ -82,6 +81,7 @@
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.After;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index ac5c59d..95ec38f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -18,7 +18,8 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.net.util.SharedLog;
+
+import com.android.net.module.util.SharedLog;
 
 /** FakeTetheringConfiguration is used to override static method for testing. */
 public class FakeTetheringConfiguration extends TetheringConfiguration {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
index f2b5314..865228a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/IPv6TetheringCoordinatorTest.java
@@ -41,11 +41,12 @@
 import android.net.NetworkCapabilities;
 import android.net.RouteInfo;
 import android.net.ip.IpServer;
-import android.net.util.SharedLog;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.SharedLog;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index 8ef0c76..faca1c8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -67,7 +67,6 @@
 import android.net.NetworkStats.Entry;
 import android.net.RouteInfo;
 import android.net.netstats.provider.NetworkStatsProvider;
-import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.Handler;
 import android.os.test.TestLooper;
@@ -79,6 +78,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.TestableNetworkStatsProviderCbBinder;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index d1891ed..36b439b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -43,7 +43,6 @@
 import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
 import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.NativeHandle;
 import android.os.test.TestLooper;
@@ -55,6 +54,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.StructNfGenMsg;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index 3190f35..1a12125 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -47,7 +47,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
@@ -63,6 +62,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 773cae3..b402bc3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -144,7 +144,6 @@
 import android.net.TetheringCallbackStartedParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.TetheringInterface;
-import android.net.TetheringManager;
 import android.net.TetheringRequestParcel;
 import android.net.dhcp.DhcpLeaseParcelable;
 import android.net.dhcp.DhcpServerCallbacks;
@@ -152,11 +151,9 @@
 import android.net.dhcp.IDhcpEventCallbacks;
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.DadProxy;
-import android.net.ip.IpNeighborMonitor;
 import android.net.ip.IpServer;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
 import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
@@ -178,7 +175,6 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
-import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -189,6 +185,8 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.IpNeighborMonitor;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
 import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
 import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
@@ -219,7 +217,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
@@ -1732,7 +1729,6 @@
         private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
         private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
         private final ArrayList<List<TetheredClient>> mTetheredClients = new ArrayList<>();
-        private final ArrayList<Long> mSupportedBitmaps = new ArrayList<>();
 
         // This function will remove the recorded callbacks, so it must be called once for
         // each callback. If this is called after multiple callback, the order matters.
@@ -1785,10 +1781,6 @@
             assertTrue(leases.containsAll(result));
         }
 
-        public void expectSupportedTetheringTypes(Set<Integer> expectedTypes) {
-            assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0)));
-        }
-
         @Override
         public void onUpstreamChanged(Network network) {
             mActualUpstreams.add(network);
@@ -1821,17 +1813,11 @@
             mTetherStates.add(parcel.states);
             mOffloadStatus.add(parcel.offloadStatus);
             mTetheredClients.add(parcel.tetheredClients);
-            mSupportedBitmaps.add(parcel.supportedTypes);
         }
 
         @Override
         public void onCallbackStopped(int errorCode) { }
 
-        @Override
-        public void onSupportedTetheringTypes(long supportedBitmap) {
-            mSupportedBitmaps.add(supportedBitmap);
-        }
-
         public void assertNoUpstreamChangeCallback() {
             assertTrue(mActualUpstreams.isEmpty());
         }
@@ -2959,81 +2945,53 @@
         runStopUSBTethering();
     }
 
-    public static ArraySet<Integer> getAllSupportedTetheringTypes() {
-        return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI,
-                TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
-    }
-
     @Test
     public void testTetheringSupported() throws Exception {
-        final ArraySet<Integer> expectedTypes = getAllSupportedTetheringTypes();
-        // Check tethering is supported after initialization.
         setTetheringSupported(true /* supported */);
-        TestTetheringEventCallback callback = new TestTetheringEventCallback();
-        mTethering.registerTetheringEventCallback(callback);
-        mLooper.dispatchAll();
-        updateConfigAndVerifySupported(callback, expectedTypes);
+        updateConfigAndVerifySupported(true /* supported */);
 
         // Could disable tethering supported by settings.
         Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
-        updateConfigAndVerifySupported(callback, new ArraySet<>());
+        updateConfigAndVerifySupported(false /* supported */);
 
         // Could disable tethering supported by user restriction.
         setTetheringSupported(true /* supported */);
-        updateConfigAndVerifySupported(callback, expectedTypes);
         when(mUserManager.hasUserRestriction(
                 UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true);
-        updateConfigAndVerifySupported(callback, new ArraySet<>());
+        updateConfigAndVerifySupported(false /* supported */);
 
         // Tethering is supported if it has any supported downstream.
         setTetheringSupported(true /* supported */);
-        updateConfigAndVerifySupported(callback, expectedTypes);
-        // Usb tethering is not supported:
-        expectedTypes.remove(TETHERING_USB);
         when(mResources.getStringArray(R.array.config_tether_usb_regexs))
                 .thenReturn(new String[0]);
-        updateConfigAndVerifySupported(callback, expectedTypes);
-        // Wifi tethering is not supported:
-        expectedTypes.remove(TETHERING_WIFI);
+        updateConfigAndVerifySupported(true /* supported */);
         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[0]);
-        updateConfigAndVerifySupported(callback, expectedTypes);
-        // Bluetooth tethering is not supported:
-        expectedTypes.remove(TETHERING_BLUETOOTH);
-        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-                .thenReturn(new String[0]);
+        updateConfigAndVerifySupported(true /* supported */);
+
 
         if (isAtLeastT()) {
-            updateConfigAndVerifySupported(callback, expectedTypes);
-
-            // P2p tethering is not supported:
-            expectedTypes.remove(TETHERING_WIFI_P2P);
+            when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+                    .thenReturn(new String[0]);
+            updateConfigAndVerifySupported(true /* supported */);
             when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(callback, expectedTypes);
-            // Ncm tethering is not supported:
-            expectedTypes.remove(TETHERING_NCM);
+            updateConfigAndVerifySupported(true /* supported */);
             when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(callback, expectedTypes);
-            // Ethernet tethering (last supported type) is not supported:
-            expectedTypes.remove(TETHERING_ETHERNET);
+            updateConfigAndVerifySupported(true /* supported */);
             mForceEthernetServiceUnavailable = true;
-            updateConfigAndVerifySupported(callback, new ArraySet<>());
-
+            updateConfigAndVerifySupported(false /* supported */);
         } else {
-            // If wifi, usb and bluetooth are all not supported, all the types are not supported.
-            expectedTypes.clear();
-            updateConfigAndVerifySupported(callback, expectedTypes);
+            when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+                    .thenReturn(new String[0]);
+            updateConfigAndVerifySupported(false /* supported */);
         }
     }
 
-    private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
-            final ArraySet<Integer> expectedTypes) {
+    private void updateConfigAndVerifySupported(boolean supported) {
         sendConfigurationChanged();
-
-        assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
-        callback.expectSupportedTetheringTypes(expectedTypes);
+        assertEquals(supported, mTethering.isTetheringSupported());
     }
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index 97cebd8..9b9507b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -49,7 +49,6 @@
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -60,6 +59,7 @@
 
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.SharedLog;
 import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
 import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
 
diff --git a/bpf_progs/dscp_policy.c b/bpf_progs/dscp_policy.c
index 38e1050..92ea0e2 100644
--- a/bpf_progs/dscp_policy.c
+++ b/bpf_progs/dscp_policy.c
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-#include <linux/types.h>
 #include <linux/bpf.h>
+#include <linux/if_ether.h>
 #include <linux/if_packet.h>
 #include <linux/ip.h>
 #include <linux/ipv6.h>
-#include <linux/if_ether.h>
 #include <linux/pkt_cls.h>
 #include <linux/tcp.h>
-#include <stdint.h>
+#include <linux/types.h>
 #include <netinet/in.h>
 #include <netinet/udp.h>
+#include <stdint.h>
 #include <string.h>
 
 // The resulting .o needs to load on the Android T beta 3 bpfloader
@@ -33,21 +33,25 @@
 #include "bpf_helpers.h"
 #include "dscp_policy.h"
 
+#define ECN_MASK 3
+#define IP4_OFFSET(field, header) (header + offsetof(struct iphdr, field))
+#define UPDATE_TOS(dscp, tos) (dscp << 2) | (tos & ECN_MASK)
+#define UPDATE_PRIORITY(dscp) ((dscp >> 2) + 0x60)
+#define UPDATE_FLOW_LABEL(dscp, flow_lbl) ((dscp & 0xf) << 6) + (flow_lbl >> 6)
+
 DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
 
 DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-        AID_SYSTEM)
+                   AID_SYSTEM)
 DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-        AID_SYSTEM)
+                   AID_SYSTEM)
 DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-        AID_SYSTEM)
+                   AID_SYSTEM)
 DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, RuleEntry, MAX_POLICIES,
-        AID_SYSTEM)
+                   AID_SYSTEM)
 
-DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
-        AID_SYSTEM)
-DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES,
-        AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
+DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, DscpPolicy, MAX_POLICIES, AID_SYSTEM)
 
 static inline __always_inline void match_policy(struct __sk_buff* skb, bool ipv4, bool is_eth) {
     void* data = (void*)(long)skb->data;
@@ -69,21 +73,21 @@
 
     // used for map lookup
     uint64_t cookie = bpf_get_socket_cookie(skb);
-    if (!cookie)
-        return;
+    if (!cookie) return;
 
     uint16_t sport = 0;
     uint16_t dport = 0;
-    uint8_t protocol = 0; // TODO: Use are reserved value? Or int (-1) and cast to uint below?
+    uint8_t protocol = 0;  // TODO: Use are reserved value? Or int (-1) and cast to uint below?
     struct in6_addr srcIp = {};
     struct in6_addr dstIp = {};
-    uint8_t tos = 0; // Only used for IPv4
-    uint8_t priority = 0; // Only used for IPv6
-    uint8_t flow_lbl = 0; // Only used for IPv6
+    uint8_t tos = 0;       // Only used for IPv4
+    uint8_t priority = 0;  // Only used for IPv6
+    uint8_t flow_lbl = 0;  // Only used for IPv6
     if (ipv4) {
         const struct iphdr* const iph = is_eth ? (void*)(eth + 1) : data;
+        hdr_size = l2_header_size + sizeof(struct iphdr);
         // Must have ipv4 header
-        if (data + l2_header_size + sizeof(*iph) > data_end) return;
+        if (data + hdr_size > data_end) return;
 
         // IP version must be 4
         if (iph->version != 4) return;
@@ -100,11 +104,11 @@
         dstIp.s6_addr32[3] = iph->daddr;
         protocol = iph->protocol;
         tos = iph->tos;
-        hdr_size = sizeof(struct iphdr);
     } else {
         struct ipv6hdr* ip6h = is_eth ? (void*)(eth + 1) : data;
+        hdr_size = l2_header_size + sizeof(struct ipv6hdr);
         // Must have ipv6 header
-        if (data + l2_header_size + sizeof(*ip6h) > data_end) return;
+        if (data + hdr_size > data_end) return;
 
         if (ip6h->version != 6) return;
 
@@ -113,29 +117,24 @@
         protocol = ip6h->nexthdr;
         priority = ip6h->priority;
         flow_lbl = ip6h->flow_lbl[0];
-        hdr_size = sizeof(struct ipv6hdr);
     }
 
     switch (protocol) {
         case IPPROTO_UDP:
-        case IPPROTO_UDPLITE:
-        {
-            struct udphdr *udp;
+        case IPPROTO_UDPLITE: {
+            struct udphdr* udp;
             udp = data + hdr_size;
             if ((void*)(udp + 1) > data_end) return;
             sport = udp->source;
             dport = udp->dest;
-        }
-        break;
-        case IPPROTO_TCP:
-        {
-            struct tcphdr *tcp;
+        } break;
+        case IPPROTO_TCP: {
+            struct tcphdr* tcp;
             tcp = data + hdr_size;
             if ((void*)(tcp + 1) > data_end) return;
             sport = tcp->source;
             dport = tcp->dest;
-        }
-        break;
+        } break;
         default:
             return;
     }
@@ -156,22 +155,19 @@
     }
 
     if (existingRule && v6_equal(srcIp, existingRule->srcIp) &&
-                v6_equal(dstIp, existingRule->dstIp) &&
-                skb->ifindex == existingRule->ifindex &&
-                ntohs(sport) == htons(existingRule->srcPort) &&
-                ntohs(dport) == htons(existingRule->dstPort) &&
-                protocol == existingRule->proto) {
+        v6_equal(dstIp, existingRule->dstIp) && skb->ifindex == existingRule->ifindex &&
+        ntohs(sport) == htons(existingRule->srcPort) &&
+        ntohs(dport) == htons(existingRule->dstPort) && protocol == existingRule->proto) {
         if (ipv4) {
-            int ecn = tos & 3;
-            uint8_t newDscpVal = (existingRule->dscpVal << 2) + ecn;
-            int oldDscpVal = tos >> 2;
-            bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
-            bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
+            uint8_t newTos = UPDATE_TOS(existingRule->dscpVal, tos);
+            bpf_l3_csum_replace(skb, IP4_OFFSET(check, l2_header_size), htons(tos), htons(newTos),
+                                sizeof(uint16_t));
+            bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &newTos, sizeof(newTos), 0);
         } else {
-            uint8_t new_priority = (existingRule->dscpVal >> 2) + 0x60;
-            uint8_t new_flow_label = ((existingRule->dscpVal & 0xf) << 6) + (priority >> 6);
-            bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
-            bpf_skb_store_bytes(skb, 1, &new_flow_label, sizeof(uint8_t), 0);
+            uint8_t new_priority = UPDATE_PRIORITY(existingRule->dscpVal);
+            uint8_t new_flow_label = UPDATE_FLOW_LABEL(existingRule->dscpVal, flow_lbl);
+            bpf_skb_store_bytes(skb, 0 + l2_header_size, &new_priority, sizeof(uint8_t), 0);
+            bpf_skb_store_bytes(skb, 1 + l2_header_size, &new_flow_label, sizeof(uint8_t), 0);
         }
         return;
     }
@@ -196,32 +192,31 @@
 
         // If the policy lookup failed, presentFields is 0, or iface index does not match
         // index on skb buff, then we can continue to next policy.
-        if (!policy || policy->presentFields == 0 || policy->ifindex != skb->ifindex)
-            continue;
+        if (!policy || policy->presentFields == 0 || policy->ifindex != skb->ifindex) continue;
 
         if ((policy->presentFields & SRC_IP_MASK_FLAG) == SRC_IP_MASK_FLAG &&
-                v6_equal(srcIp, policy->srcIp)) {
+            v6_equal(srcIp, policy->srcIp)) {
             score++;
             tempMask |= SRC_IP_MASK_FLAG;
         }
         if ((policy->presentFields & DST_IP_MASK_FLAG) == DST_IP_MASK_FLAG &&
-                v6_equal(dstIp, policy->dstIp)) {
+            v6_equal(dstIp, policy->dstIp)) {
             score++;
             tempMask |= DST_IP_MASK_FLAG;
         }
         if ((policy->presentFields & SRC_PORT_MASK_FLAG) == SRC_PORT_MASK_FLAG &&
-                ntohs(sport) == htons(policy->srcPort)) {
+            ntohs(sport) == htons(policy->srcPort)) {
             score++;
             tempMask |= SRC_PORT_MASK_FLAG;
         }
         if ((policy->presentFields & DST_PORT_MASK_FLAG) == DST_PORT_MASK_FLAG &&
-                ntohs(dport) >= htons(policy->dstPortStart) &&
-                ntohs(dport) <= htons(policy->dstPortEnd)) {
+            ntohs(dport) >= htons(policy->dstPortStart) &&
+            ntohs(dport) <= htons(policy->dstPortEnd)) {
             score++;
             tempMask |= DST_PORT_MASK_FLAG;
         }
         if ((policy->presentFields & PROTO_MASK_FLAG) == PROTO_MASK_FLAG &&
-                protocol == policy->proto) {
+            protocol == policy->proto) {
             score++;
             tempMask |= PROTO_MASK_FLAG;
         }
@@ -232,7 +227,8 @@
         }
     }
 
-    uint8_t new_tos= 0; // Can 0 be used as default forwarding value?
+    uint8_t new_tos = 0;  // Can 0 be used as default forwarding value?
+    uint8_t new_dscp = 0;
     uint8_t new_priority = 0;
     uint8_t new_flow_lbl = 0;
     if (bestScore > 0) {
@@ -244,20 +240,16 @@
         }
 
         if (policy) {
-            // TODO: if DSCP value is already set ignore?
+            new_dscp = policy->dscpVal;
             if (ipv4) {
-                int ecn = tos & 3;
-                new_tos = (policy->dscpVal << 2) + ecn;
+                new_tos = UPDATE_TOS(new_dscp, tos);
             } else {
-                new_priority = (policy->dscpVal >> 2) + 0x60;
-                new_flow_lbl = ((policy->dscpVal & 0xf) << 6) + (flow_lbl >> 6);
-
-                // Set IPv6 curDscp value to stored value and recalulate priority
-                // and flow label during next use.
-                new_tos = policy->dscpVal;
+                new_priority = UPDATE_PRIORITY(new_dscp);
+                new_flow_lbl = UPDATE_FLOW_LABEL(new_dscp, flow_lbl);
             }
         }
-    } else return;
+    } else
+        return;
 
     RuleEntry value = {
         .srcIp = srcIp,
@@ -266,10 +258,10 @@
         .srcPort = sport,
         .dstPort = dport,
         .proto = protocol,
-        .dscpVal = new_tos,
+        .dscpVal = new_dscp,
     };
 
-    //Update map with new policy.
+    // Update map with new policy.
     if (ipv4) {
         if (*selectedMap == MAP_A) {
             bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
@@ -286,12 +278,11 @@
 
     // Need to store bytes after updating map or program will not load.
     if (ipv4 && new_tos != (tos & 252)) {
-        int oldDscpVal = tos >> 2;
-        bpf_l3_csum_replace(skb, 1, oldDscpVal, new_tos, sizeof(uint8_t));
-        bpf_skb_store_bytes(skb, 1, &new_tos, sizeof(uint8_t), 0);
+        bpf_l3_csum_replace(skb, IP4_OFFSET(check, l2_header_size), htons(tos), htons(new_tos), 2);
+        bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &new_tos, sizeof(new_tos), 0);
     } else if (!ipv4 && (new_priority != priority || new_flow_lbl != flow_lbl)) {
-        bpf_skb_store_bytes(skb, 0, &new_priority, sizeof(uint8_t), 0);
-        bpf_skb_store_bytes(skb, 1, &new_flow_lbl, sizeof(uint8_t), 0);
+        bpf_skb_store_bytes(skb, l2_header_size, &new_priority, sizeof(new_priority), 0);
+        bpf_skb_store_bytes(skb, l2_header_size + 1, &new_flow_lbl, sizeof(new_flow_lbl), 0);
     }
     return;
 }
@@ -299,7 +290,6 @@
 DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM,
                      schedcls_set_dscp_ether, KVER(5, 15, 0))
 (struct __sk_buff* skb) {
-
     if (skb->pkt_type != PACKET_HOST) return TC_ACT_PIPE;
 
     if (skb->protocol == htons(ETH_P_IP)) {
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index a8f707e..76ca061 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -29,6 +29,8 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LinkPropertiesUtils;
 
 import java.net.Inet4Address;
@@ -42,7 +44,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.StringJoiner;
-import java.util.stream.Collectors;
 
 /**
  * Describes the properties of a network link.
@@ -759,20 +760,25 @@
      * @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
      */
     public @NonNull List<RouteInfo> getRoutes() {
-        if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES)) {
+        // Before T, there's no throw routes because VpnService is not updatable, so no need to
+        // filter them out.
+        if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES) || !SdkLevel.isAtLeastT()) {
             return Collections.unmodifiableList(mRoutes);
         } else {
-            return Collections.unmodifiableList(getUnicastRoutes());
+            // Apps that added a throw route themselves (not obtaining LinkProperties from the
+            // system) will not see it in getRoutes on T+ if they do not have the compat change
+            // enabled (target SDK < T); but this is expected to be rare and typically only affect
+            // tests creating LinkProperties themselves (like CTS v12, which is only running on S).
+            return Collections.unmodifiableList(getNonThrowRoutes());
         }
     }
 
     /**
-     * Returns all the {@link RouteInfo} of type {@link RouteInfo#RTN_UNICAST} set on this link.
+     * Returns all the {@link RouteInfo} that are not of type {@link RouteInfo#RTN_THROW} set on
+     * this link.
      */
-    private @NonNull List<RouteInfo> getUnicastRoutes() {
-        return mRoutes.stream()
-                .filter(route -> route.getType() == RouteInfo.RTN_UNICAST)
-                .collect(Collectors.toList());
+    private @NonNull List<RouteInfo> getNonThrowRoutes() {
+        return CollectionUtils.filter(mRoutes, route -> route.getType() != RouteInfo.RTN_THROW);
     }
 
     /**
diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java
index 0665af5..3615075 100644
--- a/framework/src/android/net/NetworkProvider.java
+++ b/framework/src/android/net/NetworkProvider.java
@@ -192,21 +192,36 @@
     private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
         @NonNull public final NetworkOfferCallback callback;
         @NonNull private final Executor mExecutor;
+        /**
+         * Boolean flag that prevents onNetworkNeeded / onNetworkUnneeded callbacks from being
+         * propagated after unregisterNetworkOffer has been called. Since unregisterNetworkOffer
+         * runs on the CS handler thread, it will not go into effect immediately.
+         */
+        private volatile boolean mIsStale;
 
         NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback,
                 @NonNull final Executor executor) {
             this.callback = callback;
             this.mExecutor = executor;
+            this.mIsStale = false;
         }
 
         @Override
         public void onNetworkNeeded(final @NonNull NetworkRequest request) {
-            mExecutor.execute(() -> callback.onNetworkNeeded(request));
+            mExecutor.execute(() -> {
+                if (!mIsStale) callback.onNetworkNeeded(request);
+            });
         }
 
         @Override
         public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
-            mExecutor.execute(() -> callback.onNetworkUnneeded(request));
+            mExecutor.execute(() -> {
+                if (!mIsStale) callback.onNetworkUnneeded(request);
+            });
+        }
+
+        public void markStale() {
+            mIsStale = true;
         }
     }
 
@@ -326,7 +341,10 @@
     public void unregisterNetworkOffer(final @NonNull NetworkOfferCallback callback) {
         final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
         if (null == proxy) return;
-        mProxies.remove(proxy);
+        synchronized (mProxies) {
+            proxy.markStale();
+            mProxies.remove(proxy);
+        }
         mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy);
     }
 }
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index b80cff4..72430d2 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -57,6 +57,9 @@
     private static final String TAG = "QosCallbackException";
 
     // Types of exceptions supported //
+    // The constants are used for the sendQosCallbackError system API, so they must not be changed
+    // as there may be callers relying on their historical values to call that API.
+    // TODO: mark the constants as @SystemApi, since they are necessary to call a system API.
     /** {@hide} */
     public static final int EX_TYPE_FILTER_NONE = 0;
 
@@ -67,13 +70,13 @@
     public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
 
     /** {@hide} */
-    public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3;
+    public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
 
     /** {@hide} */
-    public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4;
+    public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
 
     /** {@hide} */
-    public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5;
+    public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 5;
 
     /** {@hide} */
     public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6;
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 9cae9e6..788834a 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -195,6 +195,24 @@
     /**
      * Create a tap interface for testing purposes
      *
+     * @param linkAddrs an array of LinkAddresses to assign to the TAP interface
+     * @return A TestNetworkInterface representing the underlying TAP interface. Close the contained
+     *     ParcelFileDescriptor to tear down the TAP interface.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+    @NonNull
+    public TestNetworkInterface createTapInterface(@NonNull LinkAddress[] linkAddrs) {
+        try {
+            return mService.createInterface(TAP, CARRIER_UP, BRING_UP, linkAddrs, null /* iface */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Create a tap interface for testing purposes
+     *
      * @param bringUp whether to bring up the interface before returning it.
      *
      * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 7115720..8818460 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -21,7 +21,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.LinkProperties;
@@ -51,6 +50,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.PermissionUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -861,12 +861,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump " + TAG
-                    + " due to missing android.permission.DUMP permission");
-            return;
-        }
+        if (!PermissionUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
         for (ClientInfo client : mClients.values()) {
             pw.println("Client Info");
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index f058f94..dae3d2a 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -32,7 +32,6 @@
 import android.net.IpConfiguration;
 import android.net.NetworkCapabilities;
 import android.net.NetworkSpecifier;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
@@ -188,13 +187,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump EthernetService from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
-            return;
-        }
+        if (!PermissionUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
         pw.println("Current Ethernet state: ");
         pw.increaseIndent();
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 3b93f1a..b628251 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -164,16 +164,17 @@
     }
 
     public NetworkStatsFactory(@NonNull Context ctx) {
-        this(ctx, new File("/proc/"), true);
+        this(ctx, new File("/proc/"), true, new BpfNetMaps());
     }
 
     @VisibleForTesting
-    public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
+    public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats,
+            BpfNetMaps bpfNetMaps) {
         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
         mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
         mUseBpfStats = useBpfStats;
-        mBpfNetMaps = new BpfNetMaps();
+        mBpfNetMaps = bpfNetMaps;
         synchronized (mPersistentDataLock) {
             mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
             mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 7c801d7..3da1585 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -541,7 +541,8 @@
 
     /**
      * Recover from {@link FileRotator} failure by dumping state to
-     * {@link DropBoxManager} and deleting contents.
+     * {@link DropBoxManager} and deleting contents if this recorder
+     * sets {@code mWipeOnError} to true, otherwise keep the contents.
      */
     void recoverAndDeleteData() {
         if (DUMP_BEFORE_DELETE) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 9283ae2..fef6afb 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -24,7 +24,6 @@
 import static android.content.Intent.ACTION_UID_REMOVED;
 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;
@@ -2816,24 +2815,6 @@
         return stats;
     }
 
-    // TODO: It is copied from ConnectivityService, consider refactor these check permission
-    //  functions to a proper util.
-    private boolean checkAnyPermissionOf(String... permissions) {
-        for (String permission : permissions) {
-            if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void enforceAnyPermissionOf(String... permissions) {
-        if (!checkAnyPermissionOf(permissions)) {
-            throw new SecurityException("Requires one of the following permissions: "
-                    + String.join(", ", permissions) + ".");
-        }
-    }
-
     /**
      * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
      * statistics that cannot be seen by the kernel to system. To unregister, invoke the
@@ -2848,7 +2829,7 @@
      */
     public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
             @NonNull String tag, @NonNull INetworkStatsProvider provider) {
-        enforceAnyPermissionOf(NETWORK_STATS_PROVIDER,
+        PermissionUtils.enforceAnyPermissionOf(mContext, NETWORK_STATS_PROVIDER,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
         Objects.requireNonNull(provider, "provider is null");
         Objects.requireNonNull(tag, "tag is null");
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4013d2e..1ad75e3 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -92,7 +92,6 @@
 rule android.net.util.KeepalivePacketDataUtil* com.android.connectivity.@0
 
 # From connectivity-module-utils
-rule android.net.util.SharedLog* com.android.connectivity.@0
 rule android.net.shared.** com.android.connectivity.@0
 
 # From services-connectivity-shared-srcs
diff --git a/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java b/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
index 31c62f5..431f1fd 100644
--- a/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
+++ b/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
@@ -16,9 +16,10 @@
 
 package com.android.server.connectivity.mdns.util;
 
-import android.net.util.SharedLog;
 import android.text.TextUtils;
 
+import com.android.net.module.util.SharedLog;
+
 /**
  * The logger used in mDNS.
  */
@@ -58,4 +59,4 @@
     public void w(String message) {
         mLog.w(message);
     }
-}
\ No newline at end of file
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 6e107c2..d7c5a06 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -24,7 +24,11 @@
 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_DENY;
 import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.ENODEV;
+import static android.system.OsConstants.ENOENT;
 import static android.system.OsConstants.EOPNOTSUPP;
 
 import android.net.INetd;
@@ -33,8 +37,8 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.Log;
-import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BpfMap;
@@ -49,10 +53,17 @@
  * {@hide}
  */
 public class BpfNetMaps {
+    private static final boolean PRE_T = !SdkLevel.isAtLeastT();
+    static {
+        if (!PRE_T) {
+            System.loadLibrary("service-connectivity");
+        }
+    }
+
     private static final String TAG = "BpfNetMaps";
     private final INetd mNetd;
+    private final Dependencies mDeps;
     // Use legacy netd for releases before T.
-    private static final boolean PRE_T = !SdkLevel.isAtLeastT();
     private static boolean sInitialized = false;
 
     // Lock for sConfigurationMap entry for UID_RULES_CONFIGURATION_KEY.
@@ -62,47 +73,70 @@
 
     private static final String CONFIGURATION_MAP_PATH =
             "/sys/fs/bpf/netd_shared/map_netd_configuration_map";
+    private static final String UID_OWNER_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_uid_owner_map";
     private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
     private static BpfMap<U32, U32> sConfigurationMap = null;
+    // BpfMap for UID_OWNER_MAP_PATH. This map is not accessed by others.
+    private static BpfMap<U32, UidOwnerValue> sUidOwnerMap = null;
 
     // LINT.IfChange(match_type)
-    private static final long NO_MATCH = 0;
-    private static final long HAPPY_BOX_MATCH = (1 << 0);
-    private static final long PENALTY_BOX_MATCH = (1 << 1);
-    private static final long DOZABLE_MATCH = (1 << 2);
-    private static final long STANDBY_MATCH = (1 << 3);
-    private static final long POWERSAVE_MATCH = (1 << 4);
-    private static final long RESTRICTED_MATCH = (1 << 5);
-    private static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
-    private static final long IIF_MATCH = (1 << 7);
-    private static final long LOCKDOWN_VPN_MATCH = (1 << 8);
-    private static final long OEM_DENY_1_MATCH = (1 << 9);
-    private static final long OEM_DENY_2_MATCH = (1 << 10);
-    private static final long OEM_DENY_3_MATCH = (1 << 11);
+    @VisibleForTesting public static final long NO_MATCH = 0;
+    @VisibleForTesting public static final long HAPPY_BOX_MATCH = (1 << 0);
+    @VisibleForTesting public static final long PENALTY_BOX_MATCH = (1 << 1);
+    @VisibleForTesting public static final long DOZABLE_MATCH = (1 << 2);
+    @VisibleForTesting public static final long STANDBY_MATCH = (1 << 3);
+    @VisibleForTesting public static final long POWERSAVE_MATCH = (1 << 4);
+    @VisibleForTesting public static final long RESTRICTED_MATCH = (1 << 5);
+    @VisibleForTesting public static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
+    @VisibleForTesting public static final long IIF_MATCH = (1 << 7);
+    @VisibleForTesting public static final long LOCKDOWN_VPN_MATCH = (1 << 8);
+    @VisibleForTesting public static final long OEM_DENY_1_MATCH = (1 << 9);
+    @VisibleForTesting public static final long OEM_DENY_2_MATCH = (1 << 10);
+    @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
     // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
 
-    // TODO: Use Java BpfMap instead of JNI code (TrafficController) for map update.
-    // Currently, BpfNetMaps uses TrafficController for map update and TrafficController
-    // (changeUidOwnerRule and toggleUidOwnerMap) also does conversion from "firewall chain" to
-    // "match". Migrating map update from JNI to Java BpfMap will solve this duplication.
-    private static final SparseLongArray FIREWALL_CHAIN_TO_MATCH = new SparseLongArray();
-    static {
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_DOZABLE, DOZABLE_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_STANDBY, STANDBY_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_POWERSAVE, POWERSAVE_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_RESTRICTED, RESTRICTED_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_1, OEM_DENY_1_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_2, OEM_DENY_2_MATCH);
-        FIREWALL_CHAIN_TO_MATCH.put(FIREWALL_CHAIN_OEM_DENY_3, OEM_DENY_3_MATCH);
+    /**
+     * Set configurationMap for test.
+     */
+    @VisibleForTesting
+    public static void setConfigurationMapForTest(BpfMap<U32, U32> configurationMap) {
+        sConfigurationMap = configurationMap;
     }
 
     /**
-     * Only tests or BpfNetMaps#ensureInitialized can call this function.
+     * Set uidOwnerMap for test.
      */
     @VisibleForTesting
-    public static void initialize(final Dependencies deps) {
-        sConfigurationMap = deps.getConfigurationMap();
+    public static void setUidOwnerMapForTest(BpfMap<U32, UidOwnerValue> uidOwnerMap) {
+        sUidOwnerMap = uidOwnerMap;
+    }
+
+    private static BpfMap<U32, U32> getConfigurationMap() {
+        try {
+            return new BpfMap<>(
+                    CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
+        } catch (ErrnoException e) {
+            throw new IllegalStateException("Cannot open netd configuration map", e);
+        }
+    }
+
+    private static BpfMap<U32, UidOwnerValue> getUidOwnerMap() {
+        try {
+            return new BpfMap<>(
+                    UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, UidOwnerValue.class);
+        } catch (ErrnoException e) {
+            throw new IllegalStateException("Cannot open uid owner map", e);
+        }
+    }
+
+    private static void setBpfMaps() {
+        if (sConfigurationMap == null) {
+            sConfigurationMap = getConfigurationMap();
+        }
+        if (sUidOwnerMap == null) {
+            sUidOwnerMap = getUidOwnerMap();
+        }
     }
 
     /**
@@ -111,11 +145,8 @@
      */
     private static synchronized void ensureInitialized() {
         if (sInitialized) return;
-        if (!PRE_T) {
-            System.loadLibrary("service-connectivity");
-            native_init();
-            initialize(new Dependencies());
-        }
+        setBpfMaps();
+        native_init();
         sInitialized = true;
     }
 
@@ -125,16 +156,10 @@
     @VisibleForTesting
     public static class Dependencies {
         /**
-         *  Get configuration BPF map.
+         * Get interface index.
          */
-        public BpfMap<U32, U32> getConfigurationMap() {
-            try {
-                return new BpfMap<>(
-                        CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U32.class);
-            } catch (ErrnoException e) {
-                Log.e(TAG, "Cannot open netd configuration map: " + e);
-                return null;
-            }
+        public int getIfIndex(final String ifName) {
+            return Os.if_nametoindex(ifName);
         }
     }
 
@@ -146,8 +171,16 @@
     }
 
     public BpfNetMaps(final INetd netd) {
-        ensureInitialized();
+        this(netd, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public BpfNetMaps(final INetd netd, final Dependencies deps) {
+        if (!PRE_T) {
+            ensureInitialized();
+        }
         mNetd = netd;
+        mDeps = deps;
     }
 
     /**
@@ -155,11 +188,50 @@
      */
     @VisibleForTesting
     public long getMatchByFirewallChain(final int chain) {
-        final long match = FIREWALL_CHAIN_TO_MATCH.get(chain, NO_MATCH);
-        if (match == NO_MATCH) {
-            throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return DOZABLE_MATCH;
+            case FIREWALL_CHAIN_STANDBY:
+                return STANDBY_MATCH;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return POWERSAVE_MATCH;
+            case FIREWALL_CHAIN_RESTRICTED:
+                return RESTRICTED_MATCH;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return LOW_POWER_STANDBY_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_1:
+                return OEM_DENY_1_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_2:
+                return OEM_DENY_2_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return OEM_DENY_3_MATCH;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
         }
-        return match;
+    }
+
+    /**
+     * Get if the chain is allow list or not.
+     *
+     * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+     * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+     */
+    @VisibleForTesting
+    public boolean isFirewallAllowList(final int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+            case FIREWALL_CHAIN_POWERSAVE:
+            case FIREWALL_CHAIN_RESTRICTED:
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return true;
+            case FIREWALL_CHAIN_STANDBY:
+            case FIREWALL_CHAIN_OEM_DENY_1:
+            case FIREWALL_CHAIN_OEM_DENY_2:
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return false;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        }
     }
 
     private void maybeThrow(final int err, final String msg) {
@@ -174,6 +246,67 @@
         }
     }
 
+    private void removeRule(final int uid, final long match, final String caller) {
+        try {
+            synchronized (sUidOwnerMap) {
+                final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+
+                if (oldMatch == null) {
+                    throw new ServiceSpecificException(ENOENT,
+                            "sUidOwnerMap does not have entry for uid: " + uid);
+                }
+
+                final UidOwnerValue newMatch = new UidOwnerValue(
+                        (match == IIF_MATCH) ? 0 : oldMatch.iif,
+                        oldMatch.rule & ~match
+                );
+
+                if (newMatch.rule == 0) {
+                    sUidOwnerMap.deleteEntry(new U32(uid));
+                } else {
+                    sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+                }
+            }
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    caller + " failed to remove rule: " + Os.strerror(e.errno));
+        }
+    }
+
+    private void addRule(final int uid, final long match, final long iif, final String caller) {
+        if (match != IIF_MATCH && iif != 0) {
+            throw new ServiceSpecificException(EINVAL,
+                    "Non-interface match must have zero interface index");
+        }
+
+        try {
+            synchronized (sUidOwnerMap) {
+                final UidOwnerValue oldMatch = sUidOwnerMap.getValue(new U32(uid));
+
+                final UidOwnerValue newMatch;
+                if (oldMatch != null) {
+                    newMatch = new UidOwnerValue(
+                            (match == IIF_MATCH) ? iif : oldMatch.iif,
+                            oldMatch.rule | match
+                    );
+                } else {
+                    newMatch = new UidOwnerValue(
+                            iif,
+                            match
+                    );
+                }
+                sUidOwnerMap.updateEntry(new U32(uid), newMatch);
+            }
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    caller + " failed to add rule: " + Os.strerror(e.errno));
+        }
+    }
+
+    private void addRule(final int uid, final long match, final String caller) {
+        addRule(uid, match, 0 /* iif */, caller);
+    }
+
     /**
      * Add naughty app bandwidth rule for specific app
      *
@@ -182,8 +315,8 @@
      *                                  cause of the failure.
      */
     public void addNaughtyApp(final int uid) {
-        final int err = native_addNaughtyApp(uid);
-        maybeThrow(err, "Unable to add naughty app");
+        throwIfPreT("addNaughtyApp is not available on pre-T devices");
+        addRule(uid, PENALTY_BOX_MATCH, "addNaughtyApp");
     }
 
     /**
@@ -194,8 +327,8 @@
      *                                  cause of the failure.
      */
     public void removeNaughtyApp(final int uid) {
-        final int err = native_removeNaughtyApp(uid);
-        maybeThrow(err, "Unable to remove naughty app");
+        throwIfPreT("removeNaughtyApp is not available on pre-T devices");
+        removeRule(uid, PENALTY_BOX_MATCH, "removeNaughtyApp");
     }
 
     /**
@@ -206,8 +339,8 @@
      *                                  cause of the failure.
      */
     public void addNiceApp(final int uid) {
-        final int err = native_addNiceApp(uid);
-        maybeThrow(err, "Unable to add nice app");
+        throwIfPreT("addNiceApp is not available on pre-T devices");
+        addRule(uid, HAPPY_BOX_MATCH, "addNiceApp");
     }
 
     /**
@@ -218,8 +351,8 @@
      *                                  cause of the failure.
      */
     public void removeNiceApp(final int uid) {
-        final int err = native_removeNiceApp(uid);
-        maybeThrow(err, "Unable to remove nice app");
+        throwIfPreT("removeNiceApp is not available on pre-T devices");
+        removeRule(uid, HAPPY_BOX_MATCH, "removeNiceApp");
     }
 
     /**
@@ -284,11 +417,13 @@
      */
     public int replaceUidChain(final String chainName, final boolean isAllowlist,
             final int[] uids) {
-        final int err = native_replaceUidChain(chainName, isAllowlist, uids);
-        if (err != 0) {
-            Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+        synchronized (sUidOwnerMap) {
+            final int err = native_replaceUidChain(chainName, isAllowlist, uids);
+            if (err != 0) {
+                Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+            }
+            return -err;
         }
-        return -err;
     }
 
     /**
@@ -301,8 +436,18 @@
      *                                  cause of the failure.
      */
     public void setUidRule(final int childChain, final int uid, final int firewallRule) {
-        final int err = native_setUidRule(childChain, uid, firewallRule);
-        maybeThrow(err, "Unable to set uid rule");
+        throwIfPreT("setUidRule is not available on pre-T devices");
+
+        final long match = getMatchByFirewallChain(childChain);
+        final boolean isAllowList = isFirewallAllowList(childChain);
+        final boolean add = (firewallRule == FIREWALL_RULE_ALLOW && isAllowList)
+                || (firewallRule == FIREWALL_RULE_DENY && !isAllowList);
+
+        if (add) {
+            addRule(uid, match, "setUidRule");
+        } else {
+            removeRule(uid, match, "setUidRule");
+        }
     }
 
     /**
@@ -327,8 +472,25 @@
             mNetd.firewallAddUidInterfaceRules(ifName, uids);
             return;
         }
-        final int err = native_addUidInterfaceRules(ifName, uids);
-        maybeThrow(err, "Unable to add uid interface rules");
+        // Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex
+        // is set to 0.
+        final int ifIndex;
+        if (ifName == null) {
+            ifIndex = 0;
+        } else {
+            ifIndex = mDeps.getIfIndex(ifName);
+            if (ifIndex == 0) {
+                throw new ServiceSpecificException(ENODEV,
+                        "Failed to get index of interface " + ifName);
+            }
+        }
+        for (final int uid: uids) {
+            try {
+                addRule(uid, IIF_MATCH, ifIndex, "addUidInterfaceRules");
+            } catch (ServiceSpecificException e) {
+                Log.e(TAG, "addRule failed uid=" + uid + " ifName=" + ifName + ", " + e);
+            }
+        }
     }
 
     /**
@@ -347,8 +509,13 @@
             mNetd.firewallRemoveUidInterfaceRules(uids);
             return;
         }
-        final int err = native_removeUidInterfaceRules(uids);
-        maybeThrow(err, "Unable to remove uid interface rules");
+        for (final int uid: uids) {
+            try {
+                removeRule(uid, IIF_MATCH, "removeUidInterfaceRules");
+            } catch (ServiceSpecificException e) {
+                Log.e(TAG, "removeRule failed uid=" + uid + ", " + e);
+            }
+        }
     }
 
     /**
@@ -360,8 +527,12 @@
      *                                  cause of the failure.
      */
     public void updateUidLockdownRule(final int uid, final boolean add) {
-        final int err = native_updateUidLockdownRule(uid, add);
-        maybeThrow(err, "Unable to update lockdown rule");
+        throwIfPreT("updateUidLockdownRule is not available on pre-T devices");
+        if (add) {
+            addRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule");
+        } else {
+            removeRule(uid, LOCKDOWN_VPN_MATCH, "updateUidLockdownRule");
+        }
     }
 
     /**
@@ -411,14 +582,23 @@
     }
 
     private static native void native_init();
+    @GuardedBy("sUidOwnerMap")
     private native int native_addNaughtyApp(int uid);
+    @GuardedBy("sUidOwnerMap")
     private native int native_removeNaughtyApp(int uid);
+    @GuardedBy("sUidOwnerMap")
     private native int native_addNiceApp(int uid);
+    @GuardedBy("sUidOwnerMap")
     private native int native_removeNiceApp(int uid);
+    @GuardedBy("sUidOwnerMap")
     private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+    @GuardedBy("sUidOwnerMap")
     private native int native_setUidRule(int childChain, int uid, int firewallRule);
+    @GuardedBy("sUidOwnerMap")
     private native int native_addUidInterfaceRules(String ifName, int[] uids);
+    @GuardedBy("sUidOwnerMap")
     private native int native_removeUidInterfaceRules(int[] uids);
+    @GuardedBy("sUidOwnerMap")
     private native int native_updateUidLockdownRule(int uid, boolean add);
     private native int native_swapActiveStatsMap();
     private native void native_setPermissionForUids(int permissions, int[] uids);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 73f2249..7050b42 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -98,6 +98,9 @@
 import static android.system.OsConstants.IPPROTO_UDP;
 
 import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
+import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
+import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
+import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
 
 import static java.util.Map.Entry;
 
@@ -1956,7 +1959,7 @@
 
     @Override
     public Network getActiveNetworkForUid(int uid, boolean ignoreBlocked) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackPermission(mContext);
         return getActiveNetworkForUidInternal(uid, ignoreBlocked);
     }
 
@@ -1979,7 +1982,7 @@
 
     @Override
     public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackPermission(mContext);
         final NetworkAgentInfo nai = getNetworkAgentInfoForUid(uid);
         if (nai == null) return null;
         return getFilteredNetworkInfo(nai, uid, ignoreBlocked);
@@ -2518,7 +2521,7 @@
     @Override
     public NetworkState[] getAllNetworkState() {
         // This contains IMSI details, so make sure the caller is privileged.
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackPermission(mContext);
 
         final ArrayList<NetworkState> result = new ArrayList<>();
         for (NetworkStateSnapshot snapshot : getAllNetworkStateSnapshots()) {
@@ -2783,15 +2786,6 @@
         setUidBlockedReasons(uid, blockedReasons);
     }
 
-    private boolean checkAnyPermissionOf(String... permissions) {
-        for (String permission : permissions) {
-            if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private boolean checkAnyPermissionOf(int pid, int uid, String... permissions) {
         for (String permission : permissions) {
             if (mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
@@ -2801,13 +2795,6 @@
         return false;
     }
 
-    private void enforceAnyPermissionOf(String... permissions) {
-        if (!checkAnyPermissionOf(permissions)) {
-            throw new SecurityException("Requires one of the following permissions: "
-                    + String.join(", ", permissions) + ".");
-        }
-    }
-
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERNET,
@@ -2867,7 +2854,7 @@
     }
 
     private void enforceSettingsPermission() {
-        enforceAnyPermissionOf(
+        enforceAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_SETTINGS,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
@@ -2875,7 +2862,7 @@
     private void enforceNetworkFactoryPermission() {
         // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
         if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
-        enforceAnyPermissionOf(
+        enforceAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_FACTORY,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
@@ -2883,7 +2870,7 @@
     private void enforceNetworkFactoryOrSettingsPermission() {
         // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
         if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
-        enforceAnyPermissionOf(
+        enforceAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_SETTINGS,
                 android.Manifest.permission.NETWORK_FACTORY,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
@@ -2892,7 +2879,7 @@
     private void enforceNetworkFactoryOrTestNetworksPermission() {
         // TODO: Check for the BLUETOOTH_STACK permission once that is in the API surface.
         if (UserHandle.getAppId(getCallingUid()) == Process.BLUETOOTH_UID) return;
-        enforceAnyPermissionOf(
+        enforceAnyPermissionOf(mContext,
                 android.Manifest.permission.MANAGE_TEST_NETWORKS,
                 android.Manifest.permission.NETWORK_FACTORY,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
@@ -2909,7 +2896,7 @@
     }
 
     private boolean checkSettingsPermission() {
-        return checkAnyPermissionOf(
+        return PermissionUtils.checkAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_SETTINGS,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
@@ -2922,27 +2909,21 @@
     }
 
     private void enforceNetworkStackOrSettingsPermission() {
-        enforceAnyPermissionOf(
-                android.Manifest.permission.NETWORK_SETTINGS,
-                android.Manifest.permission.NETWORK_STACK,
-                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+        enforceNetworkStackPermissionOr(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS);
     }
 
     private void enforceNetworkStackSettingsOrSetup() {
-        enforceAnyPermissionOf(
+        enforceNetworkStackPermissionOr(mContext,
                 android.Manifest.permission.NETWORK_SETTINGS,
-                android.Manifest.permission.NETWORK_SETUP_WIZARD,
-                android.Manifest.permission.NETWORK_STACK,
-                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+                android.Manifest.permission.NETWORK_SETUP_WIZARD);
     }
 
     private void enforceAirplaneModePermission() {
-        enforceAnyPermissionOf(
+        enforceNetworkStackPermissionOr(mContext,
                 android.Manifest.permission.NETWORK_AIRPLANE_MODE,
                 android.Manifest.permission.NETWORK_SETTINGS,
-                android.Manifest.permission.NETWORK_SETUP_WIZARD,
-                android.Manifest.permission.NETWORK_STACK,
-                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+                android.Manifest.permission.NETWORK_SETUP_WIZARD);
     }
 
     private void enforceOemNetworkPreferencesPermission() {
@@ -2958,7 +2939,7 @@
     }
 
     private boolean checkNetworkStackPermission() {
-        return checkAnyPermissionOf(
+        return PermissionUtils.checkAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
@@ -5746,7 +5727,7 @@
 
     @Override
     public void setGlobalProxy(@Nullable final ProxyInfo proxyProperties) {
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackPermission(mContext);
         mProxyTracker.setGlobalProxy(proxyProperties);
     }
 
@@ -7294,7 +7275,7 @@
         Objects.requireNonNull(initialScore, "initialScore must not be null");
         Objects.requireNonNull(networkAgentConfig, "networkAgentConfig must not be null");
         if (networkCapabilities.hasTransport(TRANSPORT_TEST)) {
-            enforceAnyPermissionOf(Manifest.permission.MANAGE_TEST_NETWORKS);
+            enforceAnyPermissionOf(mContext, Manifest.permission.MANAGE_TEST_NETWORKS);
         } else {
             enforceNetworkFactoryPermission();
         }
@@ -10307,7 +10288,8 @@
         Objects.requireNonNull(network, "network must not be null");
         Objects.requireNonNull(extras, "extras must not be null");
 
-        enforceAnyPermissionOf(android.Manifest.permission.MANAGE_TEST_NETWORKS,
+        enforceAnyPermissionOf(mContext,
+                android.Manifest.permission.MANAGE_TEST_NETWORKS,
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
         if (!nc.hasTransport(TRANSPORT_TEST)) {
@@ -10715,7 +10697,7 @@
             preferences.add(pref);
         }
 
-        PermissionUtils.enforceNetworkStackPermission(mContext);
+        enforceNetworkStackPermission(mContext);
         if (DBG) {
             log("setProfileNetworkPreferences " + profile + " to " + preferences);
         }
diff --git a/service/src/com/android/server/UidOwnerValue.java b/service/src/com/android/server/UidOwnerValue.java
new file mode 100644
index 0000000..f89e354
--- /dev/null
+++ b/service/src/com/android/server/UidOwnerValue.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.net.module.util.Struct;
+
+/** Value type for per uid traffic control configuration map  */
+public class UidOwnerValue extends Struct {
+    // Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below.
+    @Field(order = 0, type = Type.U32)
+    public final long iif;
+
+    // A bitmask of match type.
+    @Field(order = 1, type = Type.U32)
+    public final long rule;
+
+    public UidOwnerValue(final long iif, final long rule) {
+        this.iif = iif;
+        this.rule = rule;
+    }
+}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 34c6d2d..fd1ed60 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -51,7 +51,6 @@
 import android.net.INetd;
 import android.net.UidRange;
 import android.net.Uri;
-import android.net.util.SharedLog;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -70,6 +69,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.ProcessShimImpl;
 import com.android.networkstack.apishim.common.ProcessShim;
 import com.android.server.BpfNetMaps;
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 9ed2bb3..319dbf4 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1312,7 +1312,26 @@
         assertEquals(3, lp.getRoutes().size());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
+    @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    public void testExcludedRoutesDisabled_S() {
+        final LinkProperties lp = new LinkProperties();
+        assertEquals(0, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+        assertEquals(1, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
+        // RTN_THROW routes are visible on S when added by the caller (but they are not added by
+        // the system). This is uncommon usage but was tested by CTSv12.
+        assertEquals(2, lp.getRoutes().size());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
+        assertEquals(3, lp.getRoutes().size());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabled() {
@@ -1320,12 +1339,12 @@
         assertEquals(0, lp.getRoutes().size());
 
         lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
-        assertEquals(0, lp.getRoutes().size());
+        assertEquals(1, lp.getRoutes().size());
 
         lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
-        assertEquals(0, lp.getRoutes().size());
+        assertEquals(1, lp.getRoutes().size());
 
         lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
-        assertEquals(1, lp.getRoutes().size());
+        assertEquals(2, lp.getRoutes().size());
     }
 }
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 80338aa..efc24d3 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -47,6 +47,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.connectivity.resources.R
+import com.android.server.BpfNetMaps
 import com.android.server.ConnectivityService
 import com.android.server.NetworkAgentWrapper
 import com.android.server.TestNetIdManager
@@ -208,6 +209,7 @@
         doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
         doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
         doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
+        doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any())
         doAnswer { inv ->
             object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
                     inv.getArgument(2)) {
diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index c7cf040..361c968 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -22,8 +22,8 @@
 import android.net.INetworkMonitorCallbacks
 import android.net.Network
 import android.net.metrics.IpConnectivityLog
-import android.net.util.SharedLog
 import android.os.IBinder
+import com.android.net.module.util.SharedLog
 import com.android.networkstack.netlink.TcpSocketTracker
 import com.android.server.NetworkStackService
 import com.android.server.NetworkStackService.NetworkMonitorConnector
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index db39e6f..2cd3310 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -124,6 +124,9 @@
 static const set<string> INTRODUCED_T_5_4 = {
     SHARED "prog_block_bind4_block_port",
     SHARED "prog_block_bind6_block_port",
+};
+
+static const set<string> INTRODUCED_T_5_15 = {
     SHARED "prog_dscp_policy_schedcls_set_dscp_ether",
     SHARED "prog_dscp_policy_schedcls_set_dscp_raw_ip",
 };
@@ -168,6 +171,7 @@
     if (IsAtLeastT()) {
         addAll(expected, INTRODUCED_T);
         if (android::bpf::isAtLeastKernelVersion(5, 4, 0)) addAll(expected, INTRODUCED_T_5_4);
+        if (android::bpf::isAtLeastKernelVersion(5, 15, 0)) addAll(expected, INTRODUCED_T_5_15);
         removeAll(expected, REMOVED_T);
 
         addAll(unexpected, REMOVED_T);
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index e9fad9b..0718952 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -24,13 +24,26 @@
 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_DENY;
 import static android.net.INetd.PERMISSION_INTERNET;
 
+import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
+import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
+import static com.android.server.BpfNetMaps.IIF_MATCH;
+import static com.android.server.BpfNetMaps.LOCKDOWN_VPN_MATCH;
+import static com.android.server.BpfNetMaps.NO_MATCH;
+import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
+import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
+import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
 import android.net.INetd;
@@ -68,7 +81,10 @@
 
     private static final int TEST_UID = 10086;
     private static final int[] TEST_UIDS = {10002, 10003};
-    private static final String IFNAME = "wlan0";
+    private static final String TEST_IF_NAME = "wlan0";
+    private static final int TEST_IF_INDEX = 7;
+    private static final int NO_IIF = 0;
+    private static final int NULL_IIF = 0;
     private static final String CHAINNAME = "fw_dozable";
     private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
     private static final List<Integer> FIREWALL_CHAINS = List.of(
@@ -85,43 +101,41 @@
     private BpfNetMaps mBpfNetMaps;
 
     @Mock INetd mNetd;
-    private static final TestBpfMap<U32, U32> sConfigurationMap =
-            new TestBpfMap<>(U32.class, U32.class);
+    @Mock BpfNetMaps.Dependencies mDeps;
+    private final BpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
+    private final BpfMap<U32, UidOwnerValue> mUidOwnerMap =
+            new TestBpfMap<>(U32.class, UidOwnerValue.class);
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mBpfNetMaps = new BpfNetMaps(mNetd);
-        BpfNetMaps.initialize(makeDependencies());
-        sConfigurationMap.clear();
-    }
-
-    private static BpfNetMaps.Dependencies makeDependencies() {
-        return new BpfNetMaps.Dependencies() {
-            @Override
-            public BpfMap<U32, U32> getConfigurationMap() {
-                return sConfigurationMap;
-            }
-        };
+        doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+        BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
+        BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
+        mBpfNetMaps = new BpfNetMaps(mNetd, mDeps);
     }
 
     @Test
     public void testBpfNetMapsBeforeT() throws Exception {
         assumeFalse(SdkLevel.isAtLeastT());
-        mBpfNetMaps.addUidInterfaceRules(IFNAME, TEST_UIDS);
-        verify(mNetd).firewallAddUidInterfaceRules(IFNAME, TEST_UIDS);
+        mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
+        verify(mNetd).firewallAddUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
         mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
         verify(mNetd).firewallRemoveUidInterfaceRules(TEST_UIDS);
         mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
         verify(mNetd).trafficSetNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
     }
 
-    private void doTestIsChainEnabled(final List<Integer> enableChains) throws Exception {
+    private long getMatch(final List<Integer> chains) {
         long match = 0;
-        for (final int chain: enableChains) {
+        for (final int chain: chains) {
             match |= mBpfNetMaps.getMatchByFirewallChain(chain);
         }
-        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+        return match;
+    }
+
+    private void doTestIsChainEnabled(final List<Integer> enableChains) throws Exception {
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(getMatch(enableChains)));
 
         for (final int chain: FIREWALL_CHAINS) {
             final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
@@ -187,17 +201,17 @@
             expectedMatch |= mBpfNetMaps.getMatchByFirewallChain(chain);
         }
 
-        assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+        assertEquals(0, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
 
         for (final int chain: testChains) {
             mBpfNetMaps.setChildChain(chain, true /* enable */);
         }
-        assertEquals(expectedMatch, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+        assertEquals(expectedMatch, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
 
         for (final int chain: testChains) {
             mBpfNetMaps.setChildChain(chain, false /* enable */);
         }
-        assertEquals(0, sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
+        assertEquals(0, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
     }
 
     private void doTestSetChildChain(final int testChain) throws Exception {
@@ -207,7 +221,7 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testSetChildChain() throws Exception {
-        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
         doTestSetChildChain(FIREWALL_CHAIN_DOZABLE);
         doTestSetChildChain(FIREWALL_CHAIN_STANDBY);
         doTestSetChildChain(FIREWALL_CHAIN_POWERSAVE);
@@ -221,7 +235,7 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testSetChildChainMultipleChain() throws Exception {
-        sConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+        mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
         doTestSetChildChain(List.of(
                 FIREWALL_CHAIN_DOZABLE,
                 FIREWALL_CHAIN_STANDBY));
@@ -249,4 +263,390 @@
         assertThrows(UnsupportedOperationException.class,
                 () -> mBpfNetMaps.setChildChain(FIREWALL_CHAIN_DOZABLE, true /* enable */));
     }
+
+    private void checkUidOwnerValue(final long uid, final long expectedIif,
+            final long expectedMatch) throws Exception {
+        final UidOwnerValue config = mUidOwnerMap.getValue(new U32(uid));
+        if (expectedMatch == 0) {
+            assertNull(config);
+        } else {
+            assertEquals(expectedIif, config.iif);
+            assertEquals(expectedMatch, config.rule);
+        }
+    }
+
+    private void doTestRemoveNaughtyApp(final long iif, final long match) throws Exception {
+        mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+
+        mBpfNetMaps.removeNaughtyApp(TEST_UID);
+
+        checkUidOwnerValue(TEST_UID, iif, match & ~PENALTY_BOX_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testRemoveNaughtyApp() throws Exception {
+        doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH);
+
+        // PENALTY_BOX_MATCH with other matches
+        doTestRemoveNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH);
+
+        // PENALTY_BOX_MATCH with IIF_MATCH
+        doTestRemoveNaughtyApp(TEST_IF_INDEX, PENALTY_BOX_MATCH | IIF_MATCH);
+
+        // PENALTY_BOX_MATCH is not enabled
+        doTestRemoveNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testRemoveNaughtyAppMissingUid() {
+        // UidOwnerMap does not have entry for TEST_UID
+        assertThrows(ServiceSpecificException.class,
+                () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testRemoveNaughtyAppBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.removeNaughtyApp(TEST_UID));
+    }
+
+    private void doTestAddNaughtyApp(final long iif, final long match) throws Exception {
+        if (match != NO_MATCH) {
+            mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+        }
+
+        mBpfNetMaps.addNaughtyApp(TEST_UID);
+
+        checkUidOwnerValue(TEST_UID, iif, match | PENALTY_BOX_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddNaughtyApp() throws Exception {
+        doTestAddNaughtyApp(NO_IIF, NO_MATCH);
+
+        // Other matches are enabled
+        doTestAddNaughtyApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+
+        // IIF_MATCH is enabled
+        doTestAddNaughtyApp(TEST_IF_INDEX, IIF_MATCH);
+
+        // PENALTY_BOX_MATCH is already enabled
+        doTestAddNaughtyApp(NO_IIF, PENALTY_BOX_MATCH | DOZABLE_MATCH);
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testAddNaughtyAppBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.addNaughtyApp(TEST_UID));
+    }
+
+    private void doTestRemoveNiceApp(final long iif, final long match) throws Exception {
+        mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+
+        mBpfNetMaps.removeNiceApp(TEST_UID);
+
+        checkUidOwnerValue(TEST_UID, iif, match & ~HAPPY_BOX_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testRemoveNiceApp() throws Exception {
+        doTestRemoveNiceApp(NO_IIF, HAPPY_BOX_MATCH);
+
+        // HAPPY_BOX_MATCH with other matches
+        doTestRemoveNiceApp(NO_IIF, HAPPY_BOX_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH);
+
+        // HAPPY_BOX_MATCH with IIF_MATCH
+        doTestRemoveNiceApp(TEST_IF_INDEX, HAPPY_BOX_MATCH | IIF_MATCH);
+
+        // HAPPY_BOX_MATCH is not enabled
+        doTestRemoveNiceApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testRemoveNiceAppMissingUid() {
+        // UidOwnerMap does not have entry for TEST_UID
+        assertThrows(ServiceSpecificException.class,
+                () -> mBpfNetMaps.removeNiceApp(TEST_UID));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testRemoveNiceAppBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.removeNiceApp(TEST_UID));
+    }
+
+    private void doTestAddNiceApp(final long iif, final long match) throws Exception {
+        if (match != NO_MATCH) {
+            mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+        }
+
+        mBpfNetMaps.addNiceApp(TEST_UID);
+
+        checkUidOwnerValue(TEST_UID, iif, match | HAPPY_BOX_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddNiceApp() throws Exception {
+        doTestAddNiceApp(NO_IIF, NO_MATCH);
+
+        // Other matches are enabled
+        doTestAddNiceApp(NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+
+        // IIF_MATCH is enabled
+        doTestAddNiceApp(TEST_IF_INDEX, IIF_MATCH);
+
+        // HAPPY_BOX_MATCH is already enabled
+        doTestAddNiceApp(NO_IIF, HAPPY_BOX_MATCH | DOZABLE_MATCH);
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testAddNiceAppBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.addNiceApp(TEST_UID));
+    }
+
+    private void doTestUpdateUidLockdownRule(final long iif, final long match, final boolean add)
+            throws Exception {
+        if (match != NO_MATCH) {
+            mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(iif, match));
+        }
+
+        mBpfNetMaps.updateUidLockdownRule(TEST_UID, add);
+
+        final long expectedMatch = add ? match | LOCKDOWN_VPN_MATCH : match & ~LOCKDOWN_VPN_MATCH;
+        checkUidOwnerValue(TEST_UID, iif, expectedMatch);
+    }
+
+    private static final boolean ADD = true;
+    private static final boolean REMOVE = false;
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testUpdateUidLockdownRuleAddLockdown() throws Exception {
+        doTestUpdateUidLockdownRule(NO_IIF, NO_MATCH, ADD);
+
+        // Other matches are enabled
+        doTestUpdateUidLockdownRule(
+                NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH, ADD);
+
+        // IIF_MATCH is enabled
+        doTestUpdateUidLockdownRule(TEST_IF_INDEX, DOZABLE_MATCH, ADD);
+
+        // LOCKDOWN_VPN_MATCH is already enabled
+        doTestUpdateUidLockdownRule(NO_IIF, LOCKDOWN_VPN_MATCH | DOZABLE_MATCH, ADD);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testUpdateUidLockdownRuleRemoveLockdown() throws Exception {
+        doTestUpdateUidLockdownRule(NO_IIF, LOCKDOWN_VPN_MATCH, REMOVE);
+
+        // LOCKDOWN_VPN_MATCH with other matches
+        doTestUpdateUidLockdownRule(
+                NO_IIF, LOCKDOWN_VPN_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH, REMOVE);
+
+        // LOCKDOWN_VPN_MATCH with IIF_MATCH
+        doTestUpdateUidLockdownRule(TEST_IF_INDEX, LOCKDOWN_VPN_MATCH | IIF_MATCH, REMOVE);
+
+        // LOCKDOWN_VPN_MATCH is not enabled
+        doTestUpdateUidLockdownRule(NO_IIF, POWERSAVE_MATCH | RESTRICTED_MATCH, REMOVE);
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testUpdateUidLockdownRuleBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.updateUidLockdownRule(TEST_UID, true /* add */));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddUidInterfaceRules() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+
+        mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
+
+        checkUidOwnerValue(uid0, TEST_IF_INDEX, IIF_MATCH);
+        checkUidOwnerValue(uid1, TEST_IF_INDEX, IIF_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddUidInterfaceRulesWithOtherMatch() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final long match0 = DOZABLE_MATCH;
+        final long match1 = DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
+        mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(NO_IIF, match0));
+        mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NO_IIF, match1));
+
+        mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
+
+        checkUidOwnerValue(uid0, TEST_IF_INDEX, match0 | IIF_MATCH);
+        checkUidOwnerValue(uid1, TEST_IF_INDEX, match1 | IIF_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddUidInterfaceRulesWithExistingIifMatch() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final long match0 = IIF_MATCH;
+        final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
+        mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX + 1, match0));
+        mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+
+        mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS);
+
+        checkUidOwnerValue(uid0, TEST_IF_INDEX, match0);
+        checkUidOwnerValue(uid1, TEST_IF_INDEX, match1);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddUidInterfaceRulesGetIfIndexFail() {
+        doReturn(0).when(mDeps).getIfIndex(TEST_IF_NAME);
+        assertThrows(ServiceSpecificException.class,
+                () -> mBpfNetMaps.addUidInterfaceRules(TEST_IF_NAME, TEST_UIDS));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testAddUidInterfaceRulesWithNullInterface() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final long match0 = IIF_MATCH;
+        final long match1 = IIF_MATCH | DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH;
+        mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(TEST_IF_INDEX, match0));
+        mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(NULL_IIF, match1));
+
+        mBpfNetMaps.addUidInterfaceRules(null /* ifName */, TEST_UIDS);
+
+        checkUidOwnerValue(uid0, NULL_IIF, match0);
+        checkUidOwnerValue(uid1, NULL_IIF, match1);
+    }
+
+    private void doTestRemoveUidInterfaceRules(final long iif0, final long match0,
+            final long iif1, final long match1) throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        mUidOwnerMap.updateEntry(new U32(uid0), new UidOwnerValue(iif0, match0));
+        mUidOwnerMap.updateEntry(new U32(uid1), new UidOwnerValue(iif1, match1));
+
+        mBpfNetMaps.removeUidInterfaceRules(TEST_UIDS);
+
+        checkUidOwnerValue(uid0, NO_IIF, match0 & ~IIF_MATCH);
+        checkUidOwnerValue(uid1, NO_IIF, match1 & ~IIF_MATCH);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testRemoveUidInterfaceRules() throws Exception {
+        doTestRemoveUidInterfaceRules(TEST_IF_INDEX, IIF_MATCH, NULL_IIF, IIF_MATCH);
+
+        // IIF_MATCH and other matches are enabled
+        doTestRemoveUidInterfaceRules(TEST_IF_INDEX, IIF_MATCH | DOZABLE_MATCH,
+                NULL_IIF, IIF_MATCH | DOZABLE_MATCH | RESTRICTED_MATCH);
+
+        // IIF_MATCH is not enabled
+        doTestRemoveUidInterfaceRules(NO_IIF, DOZABLE_MATCH,
+                NO_IIF, DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH);
+    }
+
+    private void doTestSetUidRule(final List<Integer> testChains) throws Exception {
+        mUidOwnerMap.updateEntry(new U32(TEST_UID), new UidOwnerValue(TEST_IF_INDEX, IIF_MATCH));
+
+        for (final int chain: testChains) {
+            final int ruleToAddMatch = mBpfNetMaps.isFirewallAllowList(chain)
+                    ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+            mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToAddMatch);
+        }
+
+        checkUidOwnerValue(TEST_UID, TEST_IF_INDEX, IIF_MATCH | getMatch(testChains));
+
+        for (final int chain: testChains) {
+            final int ruleToRemoveMatch = mBpfNetMaps.isFirewallAllowList(chain)
+                    ? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
+            mBpfNetMaps.setUidRule(chain, TEST_UID, ruleToRemoveMatch);
+        }
+
+        checkUidOwnerValue(TEST_UID, TEST_IF_INDEX, IIF_MATCH);
+    }
+
+    private void doTestSetUidRule(final int testChain) throws Exception {
+        doTestSetUidRule(List.of(testChain));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRule() throws Exception {
+        doTestSetUidRule(FIREWALL_CHAIN_DOZABLE);
+        doTestSetUidRule(FIREWALL_CHAIN_STANDBY);
+        doTestSetUidRule(FIREWALL_CHAIN_POWERSAVE);
+        doTestSetUidRule(FIREWALL_CHAIN_RESTRICTED);
+        doTestSetUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestSetUidRule(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleMultipleChain() throws Exception {
+        doTestSetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY));
+        doTestSetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED));
+        doTestSetUidRule(FIREWALL_CHAINS);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleRemoveRuleFromUidWithNoRule() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_DENY));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleInvalidChain() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(-1 /* childChain */, TEST_UID, FIREWALL_RULE_ALLOW));
+        assertThrows(expected,
+                () -> mBpfNetMaps.setUidRule(1000 /* childChain */, TEST_UID, FIREWALL_RULE_ALLOW));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleInvalidRule() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, -1 /* firewallRule */));
+        assertThrows(expected, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, 1000 /* firewallRule */));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testSetUidRuleBeforeT() {
+        assertThrows(UnsupportedOperationException.class, () ->
+                mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW));
+    }
 }
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 9365bee..58d002a 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -86,10 +86,15 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class NsdServiceTest {
-
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
     private static final long CLEANUP_DELAY_MS = 500;
     private static final long TIMEOUT_MS = 500;
+    private static final String SERVICE_NAME = "a_name";
+    private static final String SERVICE_TYPE = "a_type";
+    private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
+    private static final String DOMAIN_NAME = "mytestdevice.local";
+    private static final int PORT = 2201;
+    private static final int IFACE_IDX_ANY = 0;
 
     // Records INsdManagerCallback created when NsdService#connect is called.
     // Only accessed on the test thread, since NsdService#connect is called by the NsdManager
@@ -103,6 +108,7 @@
     @Mock MDnsManager mMockMDnsM;
     HandlerThread mThread;
     TestHandler mHandler;
+    NsdService mService;
 
     private static class LinkToDeathRecorder extends Binder {
         IBinder.DeathRecipient mDr;
@@ -134,6 +140,8 @@
         doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt());
         doReturn(true).when(mMockMDnsM).resolve(
                 anyInt(), anyString(), anyString(), anyString(), anyInt());
+
+        mService = makeService();
     }
 
     @After
@@ -147,18 +155,14 @@
     @Test
     @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testPreSClients() throws Exception {
-        NsdService service = makeService();
-
         // Pre S client connected, the daemon should be started.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
         verify(mMockMDnsM, times(1)).registerEventListener(any());
         verify(mMockMDnsM, times(1)).startDaemon();
 
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         final INsdManagerCallback cb2 = getCallback();
         final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2);
         // Daemon has been started, it should not try to start it again.
@@ -178,19 +182,15 @@
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testNoDaemonStartedWhenClientsConnect() throws Exception {
-        final NsdService service = makeService();
-
         // Creating an NsdManager will not cause daemon startup.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1);
 
         // Creating another NsdManager will not cause daemon startup either.
-        connectClient(service);
-        waitForIdle();
+        connectClient(mService);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
         final INsdManagerCallback cb2 = getCallback();
@@ -216,70 +216,66 @@
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testClientRequestsAreGCedAtDisconnection() throws Exception {
-        NsdService service = makeService();
-
-        NsdManager client = connectClient(service);
-        waitForIdle();
+        final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
         verify(mMockMDnsM, never()).registerEventListener(any());
         verify(mMockMDnsM, never()).startDaemon();
 
-        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
-        request.setPort(2201);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        request.setPort(PORT);
 
         // Client registration request
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).registerEventListener(any());
-        verify(mMockMDnsM, times(1)).startDaemon();
-        verify(mMockMDnsM, times(1)).registerService(
-                eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
+        verify(mMockMDnsM).registerEventListener(any());
+        verify(mMockMDnsM).startDaemon();
+        verify(mMockMDnsM).registerService(
+                eq(2), eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
 
         // Client discovery request
         NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
-        client.discoverServices("a_type", PROTOCOL, listener2);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, listener2);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).discover(eq(3), eq("a_type"), eq(0));
+        verify(mMockMDnsM).discover(3 /* id */, SERVICE_TYPE, IFACE_IDX_ANY);
 
         // Client resolve request
         NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
         client.resolveService(request, listener3);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).resolve(
-                eq(4), eq("a_name"), eq("a_type"), eq("local."), eq(0));
+        verify(mMockMDnsM).resolve(
+                4 /* id */, SERVICE_NAME, SERVICE_TYPE, "local." /* domain */, IFACE_IDX_ANY);
 
         // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
         deathRecipient.binderDied();
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
         // checks that request are cleaned
-        verify(mMockMDnsM, times(1)).stopOperation(eq(2));
-        verify(mMockMDnsM, times(1)).stopOperation(eq(3));
-        verify(mMockMDnsM, times(1)).stopOperation(eq(4));
+        verify(mMockMDnsM).stopOperation(2 /* id */);
+        verify(mMockMDnsM).stopOperation(3 /* id */);
+        verify(mMockMDnsM).stopOperation(4 /* id */);
     }
 
     @Test
     @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)
     public void testCleanupDelayNoRequestActive() throws Exception {
-        NsdService service = makeService();
-        NsdManager client = connectClient(service);
+        final NsdManager client = connectClient(mService);
 
-        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
-        request.setPort(2201);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        request.setPort(PORT);
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).registerEventListener(any());
-        verify(mMockMDnsM, times(1)).startDaemon();
+        verify(mMockMDnsM).registerEventListener(any());
+        verify(mMockMDnsM).startDaemon();
         final INsdManagerCallback cb1 = getCallback();
         final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1);
-        verify(mMockMDnsM, times(1)).registerService(
-                eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0));
+        verify(mMockMDnsM).registerService(
+                eq(2), eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
 
         client.unregisterService(listener1);
         waitForIdle();
-        verify(mMockMDnsM, times(1)).stopOperation(eq(2));
+        verify(mMockMDnsM).stopOperation(2 /* id */);
 
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
         reset(mMockMDnsM);
@@ -291,33 +287,28 @@
 
     @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 NsdManager client = connectClient(mService);
         final int interfaceIdx = 123;
         final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
-        client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, 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),
+        verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE),
                 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);
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
 
         final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
                 discIdCaptor.getValue(),
                 IMDnsEventListener.SERVICE_FOUND,
-                serviceName,
-                serviceType,
-                domainName,
+                SERVICE_NAME,
+                SERVICE_TYPE,
+                DOMAIN_NAME,
                 interfaceIdx,
                 INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
         final IMDnsEventListener eventListener = listenerCaptor.getValue();
@@ -328,8 +319,8 @@
                 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());
+        assertEquals(SERVICE_NAME, foundInfo.getServiceName());
+        assertEquals(SERVICE_TYPE, foundInfo.getServiceType());
         assertNull(foundInfo.getHost());
         assertNull(foundInfo.getNetwork());
         assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
@@ -340,19 +331,18 @@
         waitForIdle();
 
         final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
                 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,
+                SERVICE_FULL_NAME,
+                DOMAIN_NAME,
                 servicePort,
                 new byte[0] /* txtRecord */,
                 interfaceIdx);
@@ -362,14 +352,14 @@
         waitForIdle();
 
         final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
+        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(DOMAIN_NAME),
                 eq(interfaceIdx));
 
         final String serviceAddress = "192.0.2.123";
         final GetAddressInfo addressInfo = new GetAddressInfo(
                 getAddrIdCaptor.getValue(),
                 IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
-                serviceFullName,
+                SERVICE_FULL_NAME,
                 serviceAddress,
                 interfaceIdx,
                 INetd.LOCAL_NET_ID);
@@ -380,8 +370,8 @@
                 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(SERVICE_NAME, resolvedService.getServiceName());
+        assertEquals("." + SERVICE_TYPE, resolvedService.getServiceType());
         assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
         assertEquals(servicePort, resolvedService.getPort());
         assertNull(resolvedService.getNetwork());
@@ -415,7 +405,10 @@
     }
 
     NsdManager connectClient(NsdService service) {
-        return new NsdManager(mContext, service);
+        final NsdManager nsdManager = new NsdManager(mContext, service);
+        // Wait for client registration done.
+        waitForIdle();
+        return nsdManager;
     }
 
     void verifyDelayMaybeStopDaemon(long cleanupDelayMs) throws Exception {
diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
index ece13b3..c814cc5 100644
--- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java
+++ b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
@@ -24,7 +24,6 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -32,15 +31,18 @@
 import static org.mockito.Mockito.verify;
 
 import android.annotation.UserIdInt;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.INetd;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.INetworkManagementService;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import androidx.test.filters.SmallTest;
@@ -48,14 +50,15 @@
 import com.android.server.connectivity.Vpn;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(R) // VpnManagerService is not available before R
@@ -64,18 +67,23 @@
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
-    @Spy Context mContext;
+    private static final int TIMEOUT_MS = 2_000;
+
+    @Mock Context mContext;
+    @Mock Context mSystemContext;
+    @Mock Context mUserAllContext;
     private HandlerThread mHandlerThread;
-    @Mock private Handler mHandler;
     @Mock private Vpn mVpn;
     @Mock private INetworkManagementService mNms;
     @Mock private ConnectivityManager mCm;
     @Mock private UserManager mUserManager;
     @Mock private INetd mNetd;
     @Mock private PackageManager mPackageManager;
+
     private VpnManagerServiceDependencies mDeps;
     private VpnManagerService mService;
-
+    private BroadcastReceiver mUserPresentReceiver;
+    private BroadcastReceiver mIntentReceiver;
     private final String mNotMyVpnPkg = "com.not.my.vpn";
 
     class VpnManagerServiceDependencies extends VpnManagerService.Dependencies {
@@ -107,46 +115,54 @@
 
         mHandlerThread = new HandlerThread("TestVpnManagerService");
         mDeps = new VpnManagerServiceDependencies();
-        doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+        doReturn(mUserAllContext).when(mContext).createContextAsUser(UserHandle.ALL, 0);
+        doReturn(mSystemContext).when(mContext).createContextAsUser(UserHandle.SYSTEM, 0);
         doReturn(mPackageManager).when(mContext).getPackageManager();
         setMockedPackages(mPackageManager, sPackages);
 
         mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
         mockService(mContext, UserManager.class, Context.USER_SERVICE, mUserManager);
-
-        doReturn(new Intent()).when(mContext).registerReceiver(
-                any() /* receiver */,
-                any() /* intentFilter */,
-                any() /* broadcastPermission */,
-                eq(mHandler) /* scheduler */);
         doReturn(SYSTEM_USER).when(mUserManager).getUserInfo(eq(SYSTEM_USER_ID));
+
         mService = new VpnManagerService(mContext, mDeps);
+        mService.systemReady();
+
+        final ArgumentCaptor<BroadcastReceiver> intentReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        final ArgumentCaptor<BroadcastReceiver> userPresentReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mSystemContext).registerReceiver(
+                userPresentReceiverCaptor.capture(), any(), any(), any());
+        verify(mUserAllContext, times(2)).registerReceiver(
+                intentReceiverCaptor.capture(), any(), any(), any());
+        mUserPresentReceiver = userPresentReceiverCaptor.getValue();
+        mIntentReceiver = intentReceiverCaptor.getValue();
+
+        // Add user to create vpn in mVpn
+        onUserStarted(SYSTEM_USER_ID);
+        assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
     }
 
     @Test
     public void testUpdateAppExclusionList() {
-        // Add user to create vpn in mVpn
-        mService.onUserStarted(SYSTEM_USER_ID);
-        assertNotNull(mService.mVpns.get(SYSTEM_USER_ID));
-
         // Start vpn
         mService.startVpnProfile(TEST_VPN_PKG);
         verify(mVpn).startVpnProfile(eq(TEST_VPN_PKG));
 
         // Remove package due to package replaced.
-        mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+        onPackageRemoved(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
         verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
 
         // Add package due to package replaced.
-        mService.onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
+        onPackageAdded(PKGS[0], PKG_UIDS[0], true /* isReplacing */);
         verify(mVpn, never()).refreshPlatformVpnAppExclusionList();
 
         // Remove package
-        mService.onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+        onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
         verify(mVpn).refreshPlatformVpnAppExclusionList();
 
         // Add the package back
-        mService.onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
+        onPackageAdded(PKGS[0], PKG_UIDS[0], false /* isReplacing */);
         verify(mVpn, times(2)).refreshPlatformVpnAppExclusionList();
     }
 
@@ -160,4 +176,69 @@
     public void testStopVpnProfileFromDiffPackage() {
         assertThrows(SecurityException.class, () -> mService.stopVpnProfile(mNotMyVpnPkg));
     }
+
+    @Test
+    public void testGetProvisionedVpnProfileStateFromDiffPackage() {
+        assertThrows(SecurityException.class, () ->
+                mService.getProvisionedVpnProfileState(mNotMyVpnPkg));
+    }
+
+    @Test
+    public void testGetProvisionedVpnProfileState() {
+        mService.getProvisionedVpnProfileState(TEST_VPN_PKG);
+        verify(mVpn).getProvisionedVpnProfileState(TEST_VPN_PKG);
+    }
+
+    private Intent buildIntent(String action, String packageName, int userId, int uid,
+            boolean isReplacing) {
+        final Intent intent = new Intent(action);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        intent.putExtra(Intent.EXTRA_REPLACING, isReplacing);
+        if (packageName != null) {
+            intent.setData(Uri.fromParts("package" /* scheme */, packageName, null /* fragment */));
+        }
+
+        return intent;
+    }
+
+    private void sendIntent(Intent intent) {
+        final Handler h = mHandlerThread.getThreadHandler();
+
+        // Send in handler thread.
+        h.post(() -> mIntentReceiver.onReceive(mContext, intent));
+        HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
+    }
+
+    private void onUserStarted(int userId) {
+        sendIntent(buildIntent(Intent.ACTION_USER_STARTED,
+                null /* packageName */, userId, -1 /* uid */, false /* isReplacing */));
+    }
+
+    private void onPackageAdded(String packageName, int userId, int uid, boolean isReplacing) {
+        sendIntent(buildIntent(Intent.ACTION_PACKAGE_ADDED, packageName, userId, uid, isReplacing));
+    }
+
+    private void onPackageAdded(String packageName, int uid, boolean isReplacing) {
+        onPackageAdded(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+    }
+
+    private void onPackageRemoved(String packageName, int userId, int uid, boolean isReplacing) {
+        sendIntent(buildIntent(Intent.ACTION_PACKAGE_REMOVED, packageName, userId, uid,
+                isReplacing));
+    }
+
+    private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+        onPackageRemoved(packageName, UserHandle.USER_SYSTEM, uid, isReplacing);
+    }
+
+    @Test
+    public void testReceiveIntentFromNonHandlerThread() {
+        assertThrows(IllegalStateException.class, () ->
+                mIntentReceiver.onReceive(mContext, buildIntent(Intent.ACTION_PACKAGE_REMOVED,
+                        PKGS[0], UserHandle.USER_SYSTEM, PKG_UIDS[0], true /* isReplacing */)));
+
+        assertThrows(IllegalStateException.class, () ->
+                mUserPresentReceiver.onReceive(mContext, new Intent(Intent.ACTION_USER_PRESENT)));
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 0891ee3..5c1992d 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -232,7 +232,7 @@
     private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
     private static final long TEST_TIMEOUT_MS = 500L;
     private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
-            "VPN_APP_EXCLUDED_27_com.testvpn.vpn";
+            "VPNAPPEXCLUDED_27_com.testvpn.vpn";
     static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
     private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
 
@@ -1360,6 +1360,31 @@
     }
 
     @Test
+    public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
+        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+
+        // Enable VPN always-on for TEST_VPN_PKG.
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        // Reset to verify next startVpnProfile.
+        reset(mAppOps);
+
+        vpn.stopVpnProfile(TEST_VPN_PKG);
+
+        // Reconnect the vpn with different package will cause exception.
+        assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0]));
+
+        // Reconnect the vpn again with the vpn always on package w/o exception.
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+    }
+
+    @Test
     public void testSetPackageAuthorizationVpnService() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks();
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 5400a00..f6fb45c 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -44,6 +44,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.frameworks.tests.net.R;
+import com.android.server.BpfNetMaps;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -74,6 +75,7 @@
     private File mTestProc;
     private NetworkStatsFactory mFactory;
     @Mock private Context mContext;
+    @Mock private BpfNetMaps mBpfNetMaps;
 
     @Before
     public void setUp() throws Exception {
@@ -84,7 +86,7 @@
         // applications. So in order to have a test support native library, the native code
         // related to networkStatsFactory is compiled to a minimal native library and loaded here.
         System.loadLibrary("networkstatsfactorytestjni");
-        mFactory = new NetworkStatsFactory(mContext, mTestProc, false);
+        mFactory = new NetworkStatsFactory(mContext, mTestProc, false, mBpfNetMaps);
         mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
     }