Merge "Fix ConnectivityManagerTest#testFactoryReset flaky"
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index b3f0cf2..cd914d3 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1273,8 +1273,10 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mTetherableBluetoothRegexs, mTetherableUsbRegexs,
-                    mTetherableWifiRegexs);
+            return Objects.hash(
+                    Arrays.hashCode(mTetherableBluetoothRegexs),
+                    Arrays.hashCode(mTetherableUsbRegexs),
+                    Arrays.hashCode(mTetherableWifiRegexs));
         }
 
         @Override
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ac0bbd4..05a2884 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -64,7 +64,7 @@
 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.Struct.S32;
 import com.android.net.module.util.bpf.Tether4Key;
 import com.android.net.module.util.bpf.Tether4Value;
 import com.android.net.module.util.bpf.TetherStatsKey;
@@ -575,7 +575,7 @@
             if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac,
                     NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) {
                 mLog.e("Failed to enable upstream IPv6 forwarding from "
-                        + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+                        + getIfName(downstream) + " to " + getIfName(upstream));
             }
         }
 
@@ -616,7 +616,7 @@
             if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream,
                     rule.srcMac)) {
                 mLog.e("Failed to disable upstream IPv6 forwarding from "
-                        + mInterfaceNames.get(downstream) + " to " + mInterfaceNames.get(upstream));
+                        + getIfName(downstream) + " to " + getIfName(upstream));
             }
         }
 
@@ -960,8 +960,12 @@
     }
 
     // TODO: make mInterfaceNames accessible to the shim and move this code to there.
-    private String getIfName(long ifindex) {
-        return mInterfaceNames.get((int) ifindex, Long.toString(ifindex));
+    // This function should only be used for logging/dump purposes.
+    private String getIfName(int ifindex) {
+        // TODO: return something more useful on lookup failure
+        // likely use the 'iface_index_name_map' bpf map and/or if_nametoindex
+        // perhaps should even check that all 3 match if available.
+        return mInterfaceNames.get(ifindex, Integer.toString(ifindex));
     }
 
     /**
@@ -1038,8 +1042,8 @@
         for (int i = 0; i < mStats.size(); i++) {
             final int upstreamIfindex = mStats.keyAt(i);
             final ForwardedStats stats = mStats.get(upstreamIfindex);
-            pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get(
-                    upstreamIfindex), stats.toString()));
+            pw.println(String.format("%d(%s) - %s", upstreamIfindex, getIfName(upstreamIfindex),
+                    stats.toString()));
         }
     }
     private void dumpBpfStats(@NonNull IndentingPrintWriter pw) {
@@ -1082,8 +1086,9 @@
             for (Ipv6ForwardingRule rule : rules.values()) {
                 final int upstreamIfindex = rule.upstreamIfindex;
                 pw.println(String.format("%d(%s) %d(%s) %s [%s] [%s]", upstreamIfindex,
-                        mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex,
-                        downstreamIface, rule.address.getHostAddress(), rule.srcMac, rule.dstMac));
+                        getIfName(upstreamIfindex), rule.downstreamIfindex,
+                        getIfName(rule.downstreamIfindex), rule.address.getHostAddress(),
+                        rule.srcMac, rule.dstMac));
             }
             pw.decreaseIndent();
         }
@@ -1278,18 +1283,18 @@
             pw.println("No counter support");
             return;
         }
-        try (BpfMap<U32, U32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
-                U32.class, U32.class)) {
+        try (BpfMap<S32, S32> map = new BpfMap<>(TETHER_ERROR_MAP_PATH, BpfMap.BPF_F_RDONLY,
+                S32.class, S32.class)) {
 
             map.forEach((k, v) -> {
                 String counterName;
                 try {
-                    counterName = sBpfCounterNames[(int) k.val];
+                    counterName = sBpfCounterNames[k.val];
                 } catch (IndexOutOfBoundsException e) {
                     // Should never happen because this code gets the counter name from the same
                     // include file as the BPF program that increments the counter.
                     Log.wtf(TAG, "Unknown tethering counter type " + k.val);
-                    counterName = Long.toString(k.val);
+                    counterName = Integer.toString(k.val);
                 }
                 if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
             });
@@ -1817,8 +1822,7 @@
         // TODO: Perhaps stop the coordinator.
         boolean success = updateDataLimit(upstreamIfindex);
         if (!success) {
-            final String iface = mInterfaceNames.get(upstreamIfindex);
-            mLog.e("Setting data limit for " + iface + " failed.");
+            mLog.e("Setting data limit for " + getIfName(upstreamIfindex) + " failed.");
         }
     }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
index 4283c1b..997080c 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevKey.java
@@ -22,10 +22,10 @@
 
 /** The key of BpfMap which is used for mapping interface index. */
 public class TetherDevKey extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long ifIndex;  // interface index
+    @Field(order = 0, type = Type.S32)
+    public final int ifIndex;  // interface index
 
-    public TetherDevKey(final long ifIndex) {
+    public TetherDevKey(final int ifIndex) {
         this.ifIndex = ifIndex;
     }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
index 1cd99b5..b6e0c73 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDevValue.java
@@ -22,10 +22,10 @@
 
 /** The key of BpfMap which is used for mapping interface index. */
 public class TetherDevValue extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long ifIndex;  // interface index
+    @Field(order = 0, type = Type.S32)
+    public final int ifIndex;  // interface index
 
-    public TetherDevValue(final long ifIndex) {
+    public TetherDevValue(final int ifIndex) {
         this.ifIndex = ifIndex;
     }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
index a08ad4a..e34b3f1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherDownstream6Key.java
@@ -32,8 +32,8 @@
 
 /** The key of BpfMap which is used for bpf offload. */
 public class TetherDownstream6Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif; // The input interface index.
+    @Field(order = 0, type = Type.S32)
+    public final int iif; // The input interface index.
 
     @Field(order = 1, type = Type.EUI48, padding = 2)
     public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress).
@@ -41,7 +41,7 @@
     @Field(order = 2, type = Type.ByteArray, arraysize = 16)
     public final byte[] neigh6; // The destination IPv6 address.
 
-    public TetherDownstream6Key(final long iif, @NonNull final MacAddress dstMac,
+    public TetherDownstream6Key(final int iif, @NonNull final MacAddress dstMac,
             final byte[] neigh6) {
         Objects.requireNonNull(dstMac);
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
index bc9bb47..a7e8ccf 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherLimitKey.java
@@ -22,10 +22,10 @@
 
 /** The key of BpfMap which is used for tethering per-interface limit. */
 public class TetherLimitKey extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long ifindex;  // upstream interface index
+    @Field(order = 0, type = Type.S32)
+    public final int ifindex;  // upstream interface index
 
-    public TetherLimitKey(final long ifindex) {
+    public TetherLimitKey(final int ifindex) {
         this.ifindex = ifindex;
     }
 
@@ -43,7 +43,7 @@
 
     @Override
     public int hashCode() {
-        return Long.hashCode(ifindex);
+        return Integer.hashCode(ifindex);
     }
 
     @Override
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 5191bb7..0d1b22e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -97,6 +97,7 @@
 import android.net.TetheringInterface;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringRequestParcel;
+import android.net.Uri;
 import android.net.ip.IpServer;
 import android.net.wifi.WifiClient;
 import android.net.wifi.WifiManager;
@@ -343,9 +344,8 @@
                     mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
                 });
 
-        mSettingsObserver = new SettingsObserver(mHandler);
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(TETHER_FORCE_USB_FUNCTIONS), false, mSettingsObserver);
+        mSettingsObserver = new SettingsObserver(mContext, mHandler);
+        mSettingsObserver.startObserve();
 
         mStateReceiver = new StateReceiver();
 
@@ -397,18 +397,42 @@
     }
 
     private class SettingsObserver extends ContentObserver {
-        SettingsObserver(Handler handler) {
+        private final Uri mForceUsbFunctions;
+        private final Uri mTetherSupported;
+        private final Context mContext;
+
+        SettingsObserver(Context ctx, Handler handler) {
             super(handler);
+            mContext = ctx;
+            mForceUsbFunctions = Settings.Global.getUriFor(TETHER_FORCE_USB_FUNCTIONS);
+            mTetherSupported = Settings.Global.getUriFor(Settings.Global.TETHER_SUPPORTED);
+        }
+
+        public void startObserve() {
+            mContext.getContentResolver().registerContentObserver(mForceUsbFunctions, false, this);
+            mContext.getContentResolver().registerContentObserver(mTetherSupported, false, this);
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            mLog.i("OBSERVED Settings change");
-            final boolean isUsingNcm = mConfig.isUsingNcm();
-            updateConfiguration();
-            if (isUsingNcm != mConfig.isUsingNcm()) {
-                stopTetheringInternal(TETHERING_USB);
-                stopTetheringInternal(TETHERING_NCM);
+            Log.wtf(TAG, "Should never be reached.");
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mForceUsbFunctions.equals(uri)) {
+                mLog.i("OBSERVED TETHER_FORCE_USB_FUNCTIONS settings change");
+                final boolean isUsingNcm = mConfig.isUsingNcm();
+                updateConfiguration();
+                if (isUsingNcm != mConfig.isUsingNcm()) {
+                    stopTetheringInternal(TETHERING_USB);
+                    stopTetheringInternal(TETHERING_NCM);
+                }
+            } else if (mTetherSupported.equals(uri)) {
+                mLog.i("OBSERVED TETHER_SUPPORTED settings change");
+                updateSupportedDownstreams(mConfig);
+            } else {
+                mLog.e("Unexpected settings change: " + uri);
             }
         }
     }
@@ -1322,7 +1346,9 @@
         }
 
         private void handleUserRestrictionAction() {
-            mTetheringRestriction.onUserRestrictionsChanged();
+            if (mTetheringRestriction.onUserRestrictionsChanged()) {
+                updateSupportedDownstreams(mConfig);
+            }
         }
 
         private void handleDataSaverChanged() {
@@ -1350,6 +1376,8 @@
         return getTetheredIfaces().length > 0;
     }
 
+    // TODO: Refine TetheringTest then remove UserRestrictionActionListener class and handle
+    // onUserRestrictionsChanged inside Tethering#handleUserRestrictionAction directly.
     @VisibleForTesting
     protected static class UserRestrictionActionListener {
         private final UserManager mUserMgr;
@@ -1365,7 +1393,8 @@
             mDisallowTethering = false;
         }
 
-        public void onUserRestrictionsChanged() {
+        // return whether tethering disallowed is changed.
+        public boolean onUserRestrictionsChanged() {
             // getUserRestrictions gets restriction for this process' user, which is the primary
             // user. This is fine because DISALLOW_CONFIG_TETHERING can only be set on the primary
             // user. See UserManager.DISALLOW_CONFIG_TETHERING.
@@ -1376,15 +1405,13 @@
             mDisallowTethering = newlyDisallowed;
 
             final boolean tetheringDisallowedChanged = (newlyDisallowed != prevDisallowed);
-            if (!tetheringDisallowedChanged) {
-                return;
-            }
+            if (!tetheringDisallowedChanged) return false;
 
             if (!newlyDisallowed) {
                 // Clear the restricted notification when user is allowed to have tethering
                 // function.
                 mNotificationUpdater.tetheringRestrictionLifted();
-                return;
+                return true;
             }
 
             if (mTethering.isTetheringActive()) {
@@ -1395,6 +1422,8 @@
                 // Untether from all downstreams since tethering is disallowed.
                 mTethering.untetherAll();
             }
+
+            return true;
             // TODO(b/148139325): send tetheringSupported on restriction change
         }
     }
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index 536ab2d..0e8b044 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -98,7 +98,7 @@
         assertTrue(mTestMap.isEmpty());
     }
 
-    private TetherDownstream6Key createTetherDownstream6Key(long iif, String mac,
+    private TetherDownstream6Key createTetherDownstream6Key(int iif, String mac,
             String address) throws Exception {
         final MacAddress dstMac = MacAddress.fromString(mac);
         final InetAddress ipv6Address = InetAddress.getByName(address);
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 63bb731..b100f58 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -222,7 +222,7 @@
 
     private static class TestUpstream4Key {
         public static class Builder {
-            private long mIif = DOWNSTREAM_IFINDEX;
+            private int mIif = DOWNSTREAM_IFINDEX;
             private MacAddress mDstMac = DOWNSTREAM_MAC;
             private short mL4proto = (short) IPPROTO_TCP;
             private byte[] mSrc4 = PRIVATE_ADDR.getAddress();
@@ -246,7 +246,7 @@
 
     private static class TestDownstream4Key {
         public static class Builder {
-            private long mIif = UPSTREAM_IFINDEX;
+            private int mIif = UPSTREAM_IFINDEX;
             private MacAddress mDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
             private short mL4proto = (short) IPPROTO_TCP;
             private byte[] mSrc4 = REMOTE_ADDR.getAddress();
@@ -270,7 +270,7 @@
 
     private static class TestUpstream4Value {
         public static class Builder {
-            private long mOif = UPSTREAM_IFINDEX;
+            private int mOif = UPSTREAM_IFINDEX;
             private MacAddress mEthDstMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
             private MacAddress mEthSrcMac = MacAddress.ALL_ZEROS_ADDRESS /* dstMac (rawip) */;
             private int mEthProto = ETH_P_IP;
@@ -290,7 +290,7 @@
 
     private static class TestDownstream4Value {
         public static class Builder {
-            private long mOif = DOWNSTREAM_IFINDEX;
+            private int mOif = DOWNSTREAM_IFINDEX;
             private MacAddress mEthDstMac = MAC_A /* client mac */;
             private MacAddress mEthSrcMac = DOWNSTREAM_MAC;
             private int mEthProto = ETH_P_IP;
@@ -941,11 +941,11 @@
 
     @Test
     public void testRuleMakeTetherDownstream6Key() throws Exception {
-        final Integer mobileIfIndex = 100;
+        final int mobileIfIndex = 100;
         final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
 
         final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
-        assertEquals(key.iif, (long) mobileIfIndex);
+        assertEquals(key.iif, mobileIfIndex);
         assertEquals(key.dstMac, MacAddress.ALL_ZEROS_ADDRESS);  // rawip upstream
         assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress()));
         // iif (4) + dstMac(6) + padding(2) + neigh6 (16) = 28.
@@ -954,7 +954,7 @@
 
     @Test
     public void testRuleMakeTether6Value() throws Exception {
-        final Integer mobileIfIndex = 100;
+        final int mobileIfIndex = 100;
         final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
 
         final Tether6Value value = rule.makeTether6Value();
@@ -974,7 +974,7 @@
         final BpfCoordinator coordinator = makeBpfCoordinator();
 
         final String mobileIface = "rmnet_data0";
-        final Integer mobileIfIndex = 100;
+        final int mobileIfIndex = 100;
         coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
 
         // [1] Default limit.
@@ -1018,7 +1018,7 @@
         final BpfCoordinator coordinator = makeBpfCoordinator();
 
         final String mobileIface = "rmnet_data0";
-        final Integer mobileIfIndex = 100;
+        final int mobileIfIndex = 100;
         coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
 
         // Applying a data limit to the current upstream does not take any immediate action.
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 66ad167..a36d67f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2899,9 +2899,13 @@
     }
 
     private void forceUsbTetheringUse(final int function) {
-        Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS, function);
+        setSetting(TETHER_FORCE_USB_FUNCTIONS, function);
+    }
+
+    private void setSetting(final String key, final int value) {
+        Settings.Global.putInt(mContentResolver, key, value);
         final ContentObserver observer = mTethering.getSettingsObserverForTest();
-        observer.onChange(false /* selfChange */);
+        observer.onChange(false /* selfChange */, Settings.Global.getUriFor(key));
         mLooper.dispatchAll();
     }
 
@@ -2957,74 +2961,86 @@
                 TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET });
     }
 
+    private void setUserRestricted(boolean restricted) {
+        final Bundle restrictions = new Bundle();
+        restrictions.putBoolean(UserManager.DISALLOW_CONFIG_TETHERING, restricted);
+        when(mUserManager.getUserRestrictions()).thenReturn(restrictions);
+        when(mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(restricted);
+
+        final Intent intent = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        mLooper.dispatchAll();
+    }
+
     @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);
+        verifySupported(callback, expectedTypes);
 
-        // Could disable tethering supported by settings.
-        Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0);
-        updateConfigAndVerifySupported(callback, new ArraySet<>());
+        // Could change tethering supported by settings.
+        setSetting(Settings.Global.TETHER_SUPPORTED, 0);
+        verifySupported(callback, new ArraySet<>());
+        setSetting(Settings.Global.TETHER_SUPPORTED, 1);
+        verifySupported(callback, expectedTypes);
 
-        // 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<>());
+        // Could change tethering supported by user restriction.
+        setUserRestricted(true /* restricted */);
+        verifySupported(callback, new ArraySet<>());
+        setUserRestricted(false /* restricted */);
+        verifySupported(callback, expectedTypes);
 
-        // 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);
+        sendConfigurationChanged();
+        verifySupported(callback, expectedTypes);
         // Wifi tethering is not supported:
         expectedTypes.remove(TETHERING_WIFI);
         when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
                 .thenReturn(new String[0]);
-        updateConfigAndVerifySupported(callback, expectedTypes);
+        sendConfigurationChanged();
+        verifySupported(callback, expectedTypes);
         // Bluetooth tethering is not supported:
         expectedTypes.remove(TETHERING_BLUETOOTH);
         when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
 
         if (isAtLeastT()) {
-            updateConfigAndVerifySupported(callback, expectedTypes);
+            sendConfigurationChanged();
+            verifySupported(callback, expectedTypes);
 
             // P2p tethering is not supported:
             expectedTypes.remove(TETHERING_WIFI_P2P);
             when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(callback, expectedTypes);
+            sendConfigurationChanged();
+            verifySupported(callback, expectedTypes);
             // Ncm tethering is not supported:
             expectedTypes.remove(TETHERING_NCM);
             when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
                     .thenReturn(new String[0]);
-            updateConfigAndVerifySupported(callback, expectedTypes);
+            sendConfigurationChanged();
+            verifySupported(callback, expectedTypes);
             // Ethernet tethering (last supported type) is not supported:
             expectedTypes.remove(TETHERING_ETHERNET);
             mForceEthernetServiceUnavailable = true;
-            updateConfigAndVerifySupported(callback, new ArraySet<>());
-
+            sendConfigurationChanged();
+            verifySupported(callback, new ArraySet<>());
         } else {
             // If wifi, usb and bluetooth are all not supported, all the types are not supported.
-            expectedTypes.clear();
-            updateConfigAndVerifySupported(callback, expectedTypes);
+            sendConfigurationChanged();
+            verifySupported(callback, new ArraySet<>());
         }
     }
 
-    private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback,
+    private void verifySupported(final TestTetheringEventCallback callback,
             final ArraySet<Integer> expectedTypes) {
-        sendConfigurationChanged();
-
         assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported());
         callback.expectSupportedTetheringTypes(expectedTypes);
     }
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 3e4456f..72f63c6 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -62,8 +62,8 @@
     uint8_t protocol = 0;  // TODO: Use are reserved value? Or int (-1) and cast to uint below?
     struct in6_addr src_ip = {};
     struct in6_addr dst_ip = {};
-    uint8_t tos = 0;             // Only used for IPv4
-    uint32_t old_first_u32 = 0;  // Only used for IPv6
+    uint8_t tos = 0;            // Only used for IPv4
+    __be32 old_first_be32 = 0;  // Only used for IPv6
     if (ipv4) {
         const struct iphdr* const iph = (void*)(eth + 1);
         hdr_size = l2_header_size + sizeof(struct iphdr);
@@ -96,7 +96,7 @@
         src_ip = ip6h->saddr;
         dst_ip = ip6h->daddr;
         protocol = ip6h->nexthdr;
-        old_first_u32 = *(uint32_t*)ip6h;
+        old_first_be32 = *(__be32*)ip6h;
     }
 
     switch (protocol) {
@@ -135,9 +135,9 @@
                                 sizeof(uint16_t));
             bpf_skb_store_bytes(skb, IP4_OFFSET(tos, l2_header_size), &newTos, sizeof(newTos), 0);
         } else {
-            uint32_t new_first_u32 =
-                htonl(ntohl(old_first_u32) & 0xF03FFFFF | (existing_rule->dscp_val << 22));
-            bpf_skb_store_bytes(skb, l2_header_size, &new_first_u32, sizeof(uint32_t),
+            __be32 new_first_be32 =
+                htonl(ntohl(old_first_be32) & 0xF03FFFFF | (existing_rule->dscp_val << 22));
+            bpf_skb_store_bytes(skb, l2_header_size, &new_first_be32, sizeof(__be32),
                 BPF_F_RECOMPUTE_CSUM);
         }
         return;
@@ -214,8 +214,8 @@
         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 {
-        uint32_t new_first_u32 = htonl(ntohl(old_first_u32) & 0xF03FFFFF | (new_dscp << 22));
-        bpf_skb_store_bytes(skb, l2_header_size, &new_first_u32, sizeof(uint32_t),
+        __be32 new_first_be32 = htonl(ntohl(old_first_be32) & 0xF03FFFFF | (new_dscp << 22));
+        bpf_skb_store_bytes(skb, l2_header_size, &new_first_be32, sizeof(__be32),
             BPF_F_RECOMPUTE_CSUM);
     }
     return;
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Key.java b/common/src/com/android/net/module/util/bpf/Tether4Key.java
index 638576f..8273e6a 100644
--- a/common/src/com/android/net/module/util/bpf/Tether4Key.java
+++ b/common/src/com/android/net/module/util/bpf/Tether4Key.java
@@ -30,8 +30,8 @@
 
 /** Key type for downstream & upstream IPv4 forwarding maps. */
 public class Tether4Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif;
+    @Field(order = 0, type = Type.S32)
+    public final int iif;
 
     @Field(order = 1, type = Type.EUI48)
     public final MacAddress dstMac;
@@ -51,7 +51,7 @@
     @Field(order = 6, type = Type.UBE16)
     public final int dstPort;
 
-    public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
+    public Tether4Key(final int iif, @NonNull final MacAddress dstMac, final short l4proto,
             final byte[] src4, final byte[] dst4, final int srcPort,
             final int dstPort) {
         Objects.requireNonNull(dstMac);
diff --git a/common/src/com/android/net/module/util/bpf/Tether4Value.java b/common/src/com/android/net/module/util/bpf/Tether4Value.java
index de98766..74fdda2 100644
--- a/common/src/com/android/net/module/util/bpf/Tether4Value.java
+++ b/common/src/com/android/net/module/util/bpf/Tether4Value.java
@@ -30,8 +30,8 @@
 
 /** Value type for downstream & upstream IPv4 forwarding maps. */
 public class Tether4Value extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long oif;
+    @Field(order = 0, type = Type.S32)
+    public final int oif;
 
     // The ethhdr struct which is defined in uapi/linux/if_ether.h
     @Field(order = 1, type = Type.EUI48)
@@ -60,7 +60,7 @@
     @Field(order = 9, type = Type.U63)
     public final long lastUsed;
 
-    public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
+    public Tether4Value(final int oif, @NonNull final MacAddress ethDstMac,
             @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
             final byte[] src46, final byte[] dst46, final int srcPort,
             final int dstPort, final long lastUsed) {
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 11ba235..71fa8e4 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -191,6 +191,10 @@
     mTc.dump(fd, verbose);
 }
 
+static jint native_synchronizeKernelRCU(JNIEnv* env, jobject self) {
+    return -bpf::synchronizeKernelRCU();
+}
+
 /*
  * JNI registration.
  */
@@ -225,6 +229,8 @@
     (void*)native_setPermissionForUids},
     {"native_dump", "(Ljava/io/FileDescriptor;Z)V",
     (void*)native_dump},
+    {"native_synchronizeKernelRCU", "()I",
+    (void*)native_synchronizeKernelRCU},
 };
 // clang-format on
 
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index a1d0310..bd74d54 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -59,7 +59,8 @@
     }
 }
 
-static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, const char* iface) {
+static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, bool setIffMulticast,
+                            const char* iface) {
     base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
     ifreq ifr{};
 
@@ -76,8 +77,8 @@
         setTunTapCarrierEnabledImpl(env, iface, tun.get(), hasCarrier);
     }
 
-    // Mark TAP interfaces as supporting multicast
-    if (!isTun) {
+    // Mark some TAP interfaces as supporting multicast
+    if (setIffMulticast && !isTun) {
         base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
         ifr.ifr_flags = IFF_MULTICAST;
 
@@ -122,14 +123,14 @@
 }
 
 static jint createTunTap(JNIEnv* env, jclass /* clazz */, jboolean isTun,
-                             jboolean hasCarrier, jstring jIface) {
+                             jboolean hasCarrier, jboolean setIffMulticast, jstring jIface) {
     ScopedUtfChars iface(env, jIface);
     if (!iface.c_str()) {
         jniThrowNullPointerException(env, "iface");
         return -1;
     }
 
-    return createTunTapImpl(env, isTun, hasCarrier, iface.c_str());
+    return createTunTapImpl(env, isTun, hasCarrier, setIffMulticast, iface.c_str());
 }
 
 static void bringUpInterface(JNIEnv* env, jclass /* clazz */, jstring jIface) {
@@ -145,7 +146,7 @@
 
 static const JNINativeMethod gMethods[] = {
     {"nativeSetTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V", (void*)setTunTapCarrierEnabled},
-    {"nativeCreateTunTap", "(ZZLjava/lang/String;)I", (void*)createTunTap},
+    {"nativeCreateTunTap", "(ZZZLjava/lang/String;)I", (void*)createTunTap},
     {"nativeBringUpInterface", "(Ljava/lang/String;)V", (void*)bringUpInterface},
 };
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 3db1b22..f366363 100644
--- a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -38,7 +38,7 @@
  * and the list of the subtypes in the query as a {@link Pair}. If a query is failed to build, or if
  * it can not be enqueued, then call to {@link #call()} returns {@code null}.
  */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java b/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
index ed28700..0b2066a 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -27,7 +27,7 @@
 import java.nio.charset.StandardCharsets;
 
 /** mDNS-related constants. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 @VisibleForTesting
 public final class MdnsConstants {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
index e35743c..bd47414 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
@@ -27,7 +27,7 @@
 import java.util.Objects;
 
 /** An mDNS "AAAA" or "A" record, which holds an IPv6 or IPv4 address. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 @VisibleForTesting
 public class MdnsInetAddressRecord extends MdnsRecord {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index 2b36a3c..0166815 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -22,7 +22,7 @@
 import java.util.Arrays;
 
 /** An mDNS "PTR" record, which holds a name (the "pointer"). */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 @VisibleForTesting
 public class MdnsPointerRecord extends MdnsRecord {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
index 4bfdb2c..24fb09e 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -30,7 +30,7 @@
  * Abstract base class for mDNS records. Stores the header fields and provides methods for reading
  * the record from and writing it to a packet.
  */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public abstract class MdnsRecord {
     public static final int TYPE_A = 0x0001;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
index 1305e07..9f3894f 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -25,7 +25,7 @@
 import java.util.List;
 
 /** An mDNS response. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class MdnsResponse {
     private final List<MdnsRecord> records;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 72c3156..3e5fc42 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -30,7 +30,7 @@
 import java.util.List;
 
 /** A class that decodes mDNS responses from UDP packets. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class MdnsResponseDecoder {
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index 51de3b2..7f54d96 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -24,7 +24,7 @@
 import java.util.Objects;
 
 /** An mDNS "SRV" record, which contains service information. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 @VisibleForTesting
 public class MdnsServiceRecord extends MdnsRecord {
@@ -143,7 +143,7 @@
         return super.equals(other)
                 && (servicePriority == otherRecord.servicePriority)
                 && (serviceWeight == otherRecord.serviceWeight)
-                && Objects.equals(serviceHost, otherRecord.serviceHost)
+                && Arrays.equals(serviceHost, otherRecord.serviceHost)
                 && (servicePort == otherRecord.servicePort);
     }
-}
\ No newline at end of file
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index c3a86e3..e335de9 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -39,7 +39,7 @@
  * Instance of this class sends and receives mDNS packets of a given service type and invoke
  * registered {@link MdnsServiceBrowserListener} instances.
  */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class MdnsServiceTypeClient {
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
index 241a52a..34db7f0 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -32,7 +32,7 @@
  *
  * @see MulticastSocket for javadoc of each public method.
  */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class MdnsSocket {
     private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
index e689d6c..010f761 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -46,7 +46,7 @@
  *
  * <p>See https://tools.ietf.org/html/rfc6763 (namely sections 4 and 5).
  */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 public class MdnsSocketClient {
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
index a5b5595..a364560 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -25,7 +25,7 @@
 import java.util.Objects;
 
 /** An mDNS "TXT" record, which contains a list of text strings. */
-// TODO(b/177655645): Resolve nullness suppression.
+// TODO(b/242631897): Resolve nullness suppression.
 @SuppressWarnings("nullness")
 @VisibleForTesting
 public class MdnsTextRecord extends MdnsRecord {
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 594223c..7387483 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -26,6 +26,8 @@
 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 android.net.INetd.PERMISSION_UNINSTALLED;
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.ENODEV;
 import static android.system.OsConstants.ENOENT;
@@ -45,6 +47,7 @@
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -81,10 +84,17 @@
     // BpfNetMaps acquires this lock while sequence of read, modify, and write.
     private static final Object sUidRulesConfigBpfMapLock = new Object();
 
+    // Lock for sConfigurationMap entry for CURRENT_STATS_MAP_CONFIGURATION_KEY.
+    // BpfNetMaps acquires this lock while sequence of read, modify, and write.
+    // BpfNetMaps is an only writer of this entry.
+    private static final Object sCurrentStatsMapConfigLock = new Object();
+
     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 String UID_PERMISSION_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map";
     private static final U32 UID_RULES_CONFIGURATION_KEY = new U32(0);
     private static final U32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new U32(1);
     private static final long UID_RULES_DEFAULT_CONFIGURATION = 0;
@@ -94,6 +104,7 @@
     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;
+    private static BpfMap<U32, U8> sUidPermissionMap = null;
 
     // LINT.IfChange(match_type)
     @VisibleForTesting public static final long NO_MATCH = 0;
@@ -135,6 +146,14 @@
         sUidOwnerMap = uidOwnerMap;
     }
 
+    /**
+     * Set uidPermissionMap for test.
+     */
+    @VisibleForTesting
+    public static void setUidPermissionMapForTest(BpfMap<U32, U8> uidPermissionMap) {
+        sUidPermissionMap = uidPermissionMap;
+    }
+
     private static BpfMap<U32, U32> getConfigurationMap() {
         try {
             return new BpfMap<>(
@@ -153,6 +172,15 @@
         }
     }
 
+    private static BpfMap<U32, U8> getUidPermissionMap() {
+        try {
+            return new BpfMap<>(
+                    UID_PERMISSION_MAP_PATH, BpfMap.BPF_F_RDWR, U32.class, U8.class);
+        } catch (ErrnoException e) {
+            throw new IllegalStateException("Cannot open uid permission map", e);
+        }
+    }
+
     private static void initBpfMaps() {
         if (sConfigurationMap == null) {
             sConfigurationMap = getConfigurationMap();
@@ -178,6 +206,10 @@
         } catch (ErrnoException e) {
             throw new IllegalStateException("Failed to initialize uid owner map", e);
         }
+
+        if (sUidPermissionMap == null) {
+            sUidPermissionMap = getUidPermissionMap();
+        }
     }
 
     /**
@@ -209,6 +241,13 @@
         public int getIfIndex(final String ifName) {
             return Os.if_nametoindex(ifName);
         }
+
+        /**
+         * Call synchronize_rcu()
+         */
+        public int synchronizeKernelRCU() {
+            return native_synchronizeKernelRCU();
+        }
     }
 
     /** Constructor used after T that doesn't need to use netd anymore. */
@@ -696,12 +735,40 @@
     /**
      * Request netd to change the current active network stats map.
      *
+     * @throws UnsupportedOperationException if called on pre-T devices.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
      *                                  cause of the failure.
      */
     public void swapActiveStatsMap() {
-        final int err = native_swapActiveStatsMap();
-        maybeThrow(err, "Unable to swap active stats map");
+        throwIfPreT("swapActiveStatsMap is not available on pre-T devices");
+
+        if (sEnableJavaBpfMap) {
+            try {
+                synchronized (sCurrentStatsMapConfigLock) {
+                    final long config = sConfigurationMap.getValue(
+                            CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
+                    final long newConfig = (config == STATS_SELECT_MAP_A)
+                            ? STATS_SELECT_MAP_B : STATS_SELECT_MAP_A;
+                    sConfigurationMap.updateEntry(CURRENT_STATS_MAP_CONFIGURATION_KEY,
+                            new U32(newConfig));
+                }
+            } catch (ErrnoException e) {
+                throw new ServiceSpecificException(e.errno, "Failed to swap active stats map");
+            }
+
+            // After changing the config, it's needed to make sure all the current running eBPF
+            // programs are finished and all the CPUs are aware of this config change before the old
+            // map is modified. So special hack is needed here to wait for the kernel to do a
+            // synchronize_rcu(). Once the kernel called synchronize_rcu(), the updated config will
+            // be available to all cores and the next eBPF programs triggered inside the kernel will
+            // use the new map configuration. So once this function returns it is safe to modify the
+            // old stats map without concerning about race between the kernel and userspace.
+            final int err = mDeps.synchronizeKernelRCU();
+            maybeThrow(err, "synchronizeKernelRCU failed");
+        } else {
+            final int err = native_swapActiveStatsMap();
+            maybeThrow(err, "Unable to swap active stats map");
+        }
     }
 
     /**
@@ -719,7 +786,31 @@
             mNetd.trafficSetNetPermForUids(permissions, uids);
             return;
         }
-        native_setPermissionForUids(permissions, uids);
+
+        if (sEnableJavaBpfMap) {
+            // Remove the entry if package is uninstalled or uid has only INTERNET permission.
+            if (permissions == PERMISSION_UNINSTALLED || permissions == PERMISSION_INTERNET) {
+                for (final int uid : uids) {
+                    try {
+                        sUidPermissionMap.deleteEntry(new U32(uid));
+                    } catch (ErrnoException e) {
+                        Log.e(TAG, "Failed to remove uid " + uid + " from permission map: " + e);
+                    }
+                }
+                return;
+            }
+
+            for (final int uid : uids) {
+                try {
+                    sUidPermissionMap.updateEntry(new U32(uid), new U8((short) permissions));
+                } catch (ErrnoException e) {
+                    Log.e(TAG, "Failed to set permission "
+                            + permissions + " to uid " + uid + ": " + e);
+                }
+            }
+        } else {
+            native_setPermissionForUids(permissions, uids);
+        }
     }
 
     /**
@@ -753,4 +844,5 @@
     private native int native_swapActiveStatsMap();
     private native void native_setPermissionForUids(int permissions, int[] uids);
     private native void native_dump(FileDescriptor fd, boolean verbose);
+    private static native int native_synchronizeKernelRCU();
 }
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 15d9f13..5549fbe 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -77,7 +77,7 @@
 
     // Native method stubs
     private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier,
-            @NonNull String iface);
+            boolean setIffMulticast, @NonNull String iface);
 
     private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd,
             boolean enabled);
@@ -136,8 +136,14 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
+            // Note: if the interface is brought up by ethernet, setting IFF_MULTICAST
+            // races NetUtils#setInterfaceUp(). This flag is not necessary for ethernet
+            // tests, so let's not set it when bringUp is false. See also b/242343156.
+            // In the future, we could use RTM_SETLINK with ifi_change set to set the
+            // flags atomically.
+            final boolean setIffMulticast = bringUp;
             ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
-                    nativeCreateTunTap(isTun, hasCarrier, interfaceName));
+                    nativeCreateTunTap(isTun, hasCarrier, setIffMulticast, interfaceName));
 
             // Disable DAD and remove router_solicitation_delay before assigning link addresses.
             if (disableIpv6ProvisioningDelay) {
diff --git a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
index 3a739f2..9599d4e 100644
--- a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
+++ b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
@@ -21,7 +21,7 @@
 import android.provider.DeviceConfig
 import android.util.Log
 import com.android.modules.utils.build.SdkLevel
-import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import org.junit.rules.TestRule
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f76773..8940075 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -163,8 +163,7 @@
 
             // Only statically configure the IPv4 address; for IPv6, use the SLAAC generated
             // address.
-            iface = tnm.createTapInterface(true /* disableIpv6ProvisioningDelay */,
-                    arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
+            iface = tnm.createTapInterface(arrayOf(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN)))
             assertNotNull(iface)
         }
 
@@ -224,7 +223,7 @@
         val onLinkPrefix = raResponder.prefix
         val startTime = SystemClock.elapsedRealtime()
         while (SystemClock.elapsedRealtime() - startTime < PACKET_TIMEOUT_MS) {
-            SystemClock.sleep(1 /* ms */)
+            SystemClock.sleep(50 /* ms */)
             val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
             try {
                 network.bindSocket(sock)
@@ -273,7 +272,6 @@
         val lp = LinkProperties().apply {
             addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, IP4_PREFIX_LEN))
             addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
-            addRoute(RouteInfo(IpPrefix("::/0"), TEST_ROUTER_IPV6_ADDR))
             setInterfaceName(specifier)
         }
         val config = NetworkAgentConfig.Builder().build()
@@ -318,6 +316,7 @@
     fun parseV4PacketDscp(buffer: ByteBuffer): Int {
         // Validate checksum before parsing packet.
         val calCheck = IpUtils.ipChecksum(buffer, Struct.getSize(EthernetHeader::class.java))
+        assertEquals(0, calCheck, "Invalid IPv4 header checksum")
 
         val ip_ver = buffer.get()
         val tos = buffer.get()
@@ -328,7 +327,11 @@
         val ipType = buffer.get()
         val checksum = buffer.getShort()
 
-        assertEquals(0, calCheck, "Invalid IPv4 header checksum")
+        if (ipType.toInt() == 2 /* IPPROTO_IGMP */ && ip_ver.toInt() == 0x46) {
+            // Need to ignore 'igmp v3 report' with 'router alert' option
+        } else {
+            assertEquals(0x45, ip_ver.toInt(), "Invalid IPv4 version or IPv4 options present")
+        }
         return tos.toInt().shr(2)
     }
 
@@ -339,6 +342,9 @@
         val length = buffer.getShort()
         val proto = buffer.get()
         val hop = buffer.get()
+
+        assertEquals(6, ip_ver.toInt().shr(4), "Invalid IPv6 version")
+
         // DSCP is bottom 4 bits of ip_ver and top 2 of tc.
         val ip_ver_bottom = ip_ver.toInt().and(0xf)
         val tc_dscp = tc.toInt().shr(6)
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index d8e39b4..0377160 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -27,12 +27,13 @@
 
 import android.os.ParcelFileDescriptor;
 
+import com.android.net.module.util.CollectionUtils;
+
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Predicate;
 
@@ -170,7 +171,7 @@
      */
     private static boolean isEspFailIfSpecifiedPlaintextFound(
             byte[] pkt, int spi, boolean encap, byte[] plaintext) {
-        if (Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext)) != -1) {
+        if (CollectionUtils.indexOfSubArray(pkt, plaintext) != -1) {
             fail("Banned plaintext packet found");
         }
 
diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index 2e13689..50f02d3 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -60,7 +60,7 @@
                 <action android:name=".INetworkStackInstrumentation"/>
             </intent-filter>
         </service>
-        <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+        <service android:name="com.android.networkstack.ipmemorystore.RegularMaintenanceJobService"
              android:process="com.android.server.net.integrationtests.testnetworkstack"
              android:permission="android.permission.BIND_JOB_SERVICE"/>
 
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index e3dbb14..8a4932b 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -38,7 +38,7 @@
 
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.ExceptionUtils;
+import com.android.testutils.FunctionalUtils.ThrowingConsumer;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -396,7 +396,7 @@
         }
     }
 
-    int getRequestKey(ExceptionUtils.ThrowingConsumer<ArgumentCaptor<Integer>> verifier)
+    int getRequestKey(ThrowingConsumer<ArgumentCaptor<Integer>> verifier)
             throws Exception {
         final ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
         verifier.accept(captor);
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 2d09bf2..be286ec 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -27,6 +27,10 @@
 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 android.net.INetd.PERMISSION_NONE;
+import static android.net.INetd.PERMISSION_UNINSTALLED;
+import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
+import static android.system.OsConstants.EPERM;
 
 import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
 import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
@@ -56,6 +60,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BpfMap;
 import com.android.net.module.util.Struct.U32;
+import com.android.net.module.util.Struct.U8;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -88,6 +93,7 @@
     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 U32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new U32(1);
     private static final List<Integer> FIREWALL_CHAINS = List.of(
             FIREWALL_CHAIN_DOZABLE,
             FIREWALL_CHAIN_STANDBY,
@@ -99,6 +105,9 @@
             FIREWALL_CHAIN_OEM_DENY_3
     );
 
+    private static final long STATS_SELECT_MAP_A = 0;
+    private static final long STATS_SELECT_MAP_B = 1;
+
     private BpfNetMaps mBpfNetMaps;
 
     @Mock INetd mNetd;
@@ -107,14 +116,17 @@
     private final BpfMap<U32, U32> mConfigurationMap = new TestBpfMap<>(U32.class, U32.class);
     private final BpfMap<U32, UidOwnerValue> mUidOwnerMap =
             new TestBpfMap<>(U32.class, UidOwnerValue.class);
+    private final BpfMap<U32, U8> mUidPermissionMap = new TestBpfMap<>(U32.class, U8.class);
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME);
+        doReturn(0).when(mDeps).synchronizeKernelRCU();
         BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
         BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
         BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
+        BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
         mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps);
     }
 
@@ -728,4 +740,141 @@
                 () -> mBpfNetMaps.replaceUidChain(FIREWALL_CHAIN_DOZABLE, TEST_UIDS));
     }
 
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsGrantInternetPermission() throws Exception {
+        mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+
+        assertTrue(mUidPermissionMap.isEmpty());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsGrantUpdateStatsPermission() throws Exception {
+        mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS, TEST_UIDS);
+
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsGrantMultiplePermissions() throws Exception {
+        final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+        mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsRevokeInternetPermission() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        mBpfNetMaps.setNetPermForUids(PERMISSION_INTERNET, TEST_UIDS);
+        mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsRevokeUpdateDeviceStatsPermission() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        mBpfNetMaps.setNetPermForUids(PERMISSION_UPDATE_DEVICE_STATS, TEST_UIDS);
+        mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(PERMISSION_UPDATE_DEVICE_STATS, mUidPermissionMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsRevokeMultiplePermissions() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+        mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+        mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, new int[]{uid0});
+
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsPermissionUninstalled() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+        mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+        mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, new int[]{uid0});
+
+        assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSetNetPermForUidsDuplicatedRequestSilentlyIgnored() throws Exception {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final int permission = PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS;
+
+        mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.setNetPermForUids(permission, TEST_UIDS);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(permission, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, TEST_UIDS);
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.setNetPermForUids(PERMISSION_NONE, TEST_UIDS);
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid0)).val);
+        assertEquals(PERMISSION_NONE, mUidPermissionMap.getValue(new U32(uid1)).val);
+
+        mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
+        assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+        assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+
+        mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED, TEST_UIDS);
+        assertNull(mUidPermissionMap.getValue(new U32(uid0)));
+        assertNull(mUidPermissionMap.getValue(new U32(uid1)));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSwapActiveStatsMap() throws Exception {
+        mConfigurationMap.updateEntry(
+                CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+
+        mBpfNetMaps.swapActiveStatsMap();
+        assertEquals(STATS_SELECT_MAP_B,
+                mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
+
+        mBpfNetMaps.swapActiveStatsMap();
+        assertEquals(STATS_SELECT_MAP_A,
+                mConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testSwapActiveStatsMapSynchronizeKernelRCUFail() throws Exception {
+        doReturn(EPERM).when(mDeps).synchronizeKernelRCU();
+        mConfigurationMap.updateEntry(
+                CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+
+        assertThrows(ServiceSpecificException.class, () -> mBpfNetMaps.swapActiveStatsMap());
+    }
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 4b832dd..8bf0ca5 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -159,7 +159,7 @@
 import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-import static com.android.testutils.ExceptionUtils.ignoreExceptions;
+import static com.android.testutils.FunctionalUtils.ignoreExceptions;
 import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
 import static com.android.testutils.MiscAsserts.assertContainsAll;
 import static com.android.testutils.MiscAsserts.assertContainsExactly;
@@ -378,7 +378,9 @@
 import com.android.server.net.NetworkPinner;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.ExceptionUtils;
+import com.android.testutils.FunctionalUtils.Function3;
+import com.android.testutils.FunctionalUtils.ThrowingConsumer;
+import com.android.testutils.FunctionalUtils.ThrowingRunnable;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.TestableNetworkCallback;
@@ -744,7 +746,7 @@
         }
 
         private int checkMockedPermission(String permission, int pid, int uid,
-                Supplier<Integer> ifAbsent) {
+                Function3<String, Integer, Integer, Integer> ifAbsent /* perm, uid, pid -> int */) {
             final Integer granted = mMockedPermissions.get(permission + "," + pid + "," + uid);
             if (null != granted) {
                 return granted;
@@ -753,27 +755,27 @@
             if (null != allGranted) {
                 return allGranted;
             }
-            return ifAbsent.get();
+            return ifAbsent.apply(permission, pid, uid);
         }
 
         @Override
         public int checkPermission(String permission, int pid, int uid) {
             return checkMockedPermission(permission, pid, uid,
-                    () -> super.checkPermission(permission, pid, uid));
+                    (perm, p, u) -> super.checkPermission(perm, p, u));
         }
 
         @Override
         public int checkCallingOrSelfPermission(String permission) {
             return checkMockedPermission(permission, Process.myPid(), Process.myUid(),
-                    () -> super.checkCallingOrSelfPermission(permission));
+                    (perm, p, u) -> super.checkCallingOrSelfPermission(perm));
         }
 
         @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
             final Integer granted = checkMockedPermission(permission,
                     Process.myPid(), Process.myUid(),
-                    () -> {
-                        super.enforceCallingOrSelfPermission(permission, message);
+                    (perm, p, u) -> {
+                        super.enforceCallingOrSelfPermission(perm, message);
                         // enforce will crash if the permission is not granted
                         return PERMISSION_GRANTED;
                     });
@@ -786,7 +788,7 @@
         /**
          * Mock checks for the specified permission, and have them behave as per {@code granted}.
          *
-         * This will apply across the board no matter what the checked UID and PID are.
+         * This will apply to all calls no matter what the checked UID and PID are.
          *
          * <p>Passing null reverts to default behavior, which does a real permission check on the
          * test package.
@@ -1718,11 +1720,7 @@
         });
     }
 
-    private interface ExceptionalRunnable {
-        void run() throws Exception;
-    }
-
-    private void withPermission(String permission, ExceptionalRunnable r) throws Exception {
+    private void withPermission(String permission, ThrowingRunnable r) throws Exception {
         try {
             mServiceContext.setPermission(permission, PERMISSION_GRANTED);
             r.run();
@@ -1731,7 +1729,7 @@
         }
     }
 
-    private void withPermission(String permission, int pid, int uid, ExceptionalRunnable r)
+    private void withPermission(String permission, int pid, int uid, ThrowingRunnable r)
             throws Exception {
         try {
             mServiceContext.setPermission(permission, pid, uid, PERMISSION_GRANTED);
@@ -6200,7 +6198,7 @@
     }
 
     // Helper method to prepare the executor and run test
-    private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor)
+    private void runTestWithSerialExecutors(ThrowingConsumer<Executor> functor)
             throws Exception {
         final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor();
         final Executor executorInline = (Runnable r) -> r.run();
@@ -15752,7 +15750,7 @@
         final UserHandle testHandle = setupEnterpriseNetwork();
         final TestOnCompleteListener listener = new TestOnCompleteListener();
         // Leave one request available so the profile preference can be set.
-        testRequestCountLimits(1 /* countToLeaveAvailable */, () -> {
+        withRequestCountersAcquired(1 /* countToLeaveAvailable */, () -> {
             withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                     Process.myPid(), Process.myUid(), () -> {
                         // Set initially to test the limit prior to having existing requests.
@@ -15801,7 +15799,7 @@
         @OemNetworkPreferences.OemNetworkPreference final int networkPref =
                 OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
         // Leave one request available so the OEM preference can be set.
-        testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
+        withRequestCountersAcquired(1 /* countToLeaveAvailable */, () ->
                 withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
                     // Set initially to test the limit prior to having existing requests.
                     final TestOemListenerCallback listener = new TestOemListenerCallback();
@@ -15816,8 +15814,8 @@
                 }));
     }
 
-    private void testRequestCountLimits(final int countToLeaveAvailable,
-            @NonNull final ExceptionalRunnable r) throws Exception {
+    private void withRequestCountersAcquired(final int countToLeaveAvailable,
+            @NonNull final ThrowingRunnable r) throws Exception {
         final ArraySet<TestNetworkCallback> callbacks = new ArraySet<>();
         try {
             final int requestCount = mService.mSystemNetworkRequestCounter
@@ -16084,7 +16082,7 @@
         ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext,
                 Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
         // Leave one request available so MDO preference set up above can be set.
-        testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
+        withRequestCountersAcquired(1 /* countToLeaveAvailable */, () ->
                 withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                         Process.myPid(), Process.myUid(), () -> {
                             // Set initially to test the limit prior to having existing requests.
diff --git a/tests/unit/java/com/android/server/NetIdManagerTest.kt b/tests/unit/java/com/android/server/NetIdManagerTest.kt
index 811134e..f2b14a1 100644
--- a/tests/unit/java/com/android/server/NetIdManagerTest.kt
+++ b/tests/unit/java/com/android/server/NetIdManagerTest.kt
@@ -21,7 +21,7 @@
 import com.android.server.NetIdManager.MIN_NET_ID
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
 import com.android.testutils.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 041e4ea..f159859 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -92,6 +92,7 @@
 import android.net.LinkProperties;
 import android.net.LocalSocket;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
 import android.net.RouteInfo;
@@ -264,6 +265,7 @@
         final Ikev2VpnProfile.Builder builder =
                 new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
         builder.setAuthPsk(TEST_VPN_PSK);
+        builder.setBypassable(true /* isBypassable */);
         mVpnProfile = builder.build().toVpnProfile();
     }
 
@@ -1787,9 +1789,11 @@
         ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
         ArgumentCaptor<NetworkCapabilities> ncCaptor =
                 ArgumentCaptor.forClass(NetworkCapabilities.class);
+        ArgumentCaptor<NetworkAgentConfig> nacCaptor =
+                ArgumentCaptor.forClass(NetworkAgentConfig.class);
         verify(mTestDeps).newNetworkAgent(
                 any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
-                any(), any(), any());
+                any(), nacCaptor.capture(), any());
 
         // Check LinkProperties
         final LinkProperties lp = lpCaptor.getValue();
@@ -1811,6 +1815,9 @@
         // Check NetworkCapabilities
         assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks());
 
+        // Check if allowBypass is set or not.
+        assertTrue(nacCaptor.getValue().isBypassableVpn());
+
         return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
     }
 
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
index 4c2cf54..2ff53fa 100755
--- a/tools/gen_jarjar.py
+++ b/tools/gen_jarjar.py
@@ -115,7 +115,8 @@
             jar_classes = _list_jar_classes(jar)
             jar_classes.sort()
             for clazz in jar_classes:
-                if (_get_toplevel_class(clazz) not in excluded_classes and
+                if (not clazz.startswith(args.prefix + '.') and
+                        _get_toplevel_class(clazz) not in excluded_classes and
                         not any(r.fullmatch(clazz) for r in exclude_regexes)):
                     outfile.write(f'rule {clazz} {args.prefix}.@0\n')
                     # Also include jarjar rules for unit tests of the class, so the package matches
diff --git a/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java b/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java
new file mode 100644
index 0000000..6859020
--- /dev/null
+++ b/tools/testdata/java/jarjar/prefix/AlreadyInTargetPackageClass.java
@@ -0,0 +1,25 @@
+/*
+ * 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 jarjar.prefix;
+
+/**
+ * Sample class to test jarjar rules, already in the "jarjar.prefix" package.
+ */
+public class AlreadyInTargetPackageClass {
+    /** Test inner class that should not be jarjared either */
+    public static class TestInnerClass {}
+}