Merge "Make FIREWALL_CHAIN_OEM_DENY API"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index adcc236..19d0d5f 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -179,9 +179,9 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
-    // The permission configuration *must* be included to ensure security of the device
+    // The network stack *must* be included to ensure security of the device
     required: [
-        "NetworkPermissionConfig",
+        "NetworkStack",
         "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
@@ -199,9 +199,9 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
-    // The permission configuration *must* be included to ensure security of the device
+    // The network stack *must* be included to ensure security of the device
     required: [
-        "NetworkPermissionConfig",
+        "NetworkStackNext",
         "privapp_allowlist_com.android.tethering",
     ],
     apex_available: ["com.android.tethering"],
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 92be84d..237a645 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -28,7 +28,6 @@
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringTester.RemoteResponder;
 import static android.net.TetheringTester.isIcmpv6Type;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
 import static android.system.OsConstants.IPPROTO_IP;
 import static android.system.OsConstants.IPPROTO_IPV6;
 import static android.system.OsConstants.IPPROTO_UDP;
@@ -85,7 +84,6 @@
 import com.android.net.module.util.bpf.TetherStatsKey;
 import com.android.net.module.util.bpf.TetherStatsValue;
 import com.android.net.module.util.structs.EthernetHeader;
-import com.android.net.module.util.structs.Icmpv6Header;
 import com.android.net.module.util.structs.Ipv4Header;
 import com.android.net.module.util.structs.Ipv6Header;
 import com.android.net.module.util.structs.UdpHeader;
@@ -146,6 +144,7 @@
     // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
     private static final int TX_UDP_PACKET_SIZE = 44;
     private static final int TX_UDP_PACKET_COUNT = 123;
+    private static final long WAIT_RA_TIMEOUT_MS = 2000;
 
     private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
     private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
@@ -329,27 +328,13 @@
 
     }
 
-    private static boolean isRouterAdvertisement(byte[] pkt) {
-        if (pkt == null) return false;
-
-        ByteBuffer buf = ByteBuffer.wrap(pkt);
-
-        final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
-        if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
-
-        final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
-        if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
-
-        final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
-        return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
-    }
-
-    private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
+    private static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
             long timeoutMs) {
         final long deadline = SystemClock.uptimeMillis() + timeoutMs;
         do {
             byte[] pkt = reader.popPacket(timeoutMs);
-            if (isRouterAdvertisement(pkt)) return;
+            if (isIcmpv6Type(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return;
+
             timeoutMs = deadline - SystemClock.uptimeMillis();
         } while (timeoutMs > 0);
         fail("Did not receive router advertisement on " + iface + " after "
@@ -401,7 +386,7 @@
         // before the reader is started.
         mDownstreamReader = makePacketReader(mDownstreamIface);
 
-        expectRouterAdvertisement(mDownstreamReader, iface, 2000 /* timeoutMs */);
+        waitForRouterAdvertisement(mDownstreamReader, iface, WAIT_RA_TIMEOUT_MS);
         expectLocalOnlyAddresses(iface);
     }
 
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 94d5ed8..cb1714c 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -189,6 +189,11 @@
     return *config;
 }
 
+// DROP_IF_SET is set of rules that BPF_DROP if rule is globally enabled, and per-uid bit is set
+#define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)
+// DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
+#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH)
+
 static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
     if (skip_owner_match(skb)) return BPF_PASS;
 
@@ -200,32 +205,13 @@
     uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
     uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
 
-    if (enabledRules) {
-        if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
-            return BPF_DROP;
-        }
-        if ((enabledRules & OEM_DENY_3_MATCH) && (uidRules & OEM_DENY_3_MATCH)) {
-            return BPF_DROP;
-        }
-    }
+    // Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules
+    // check whether the rules are globally enabled, and if so whether the rules are
+    // set/unset for the specific uid.  BPF_DROP if that is the case for ANY of the rules.
+    // We achieve this by masking out only the bits/rules we're interested in checking,
+    // and negating (via bit-wise xor) the bits/rules that should drop if unset.
+    if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return BPF_DROP;
+
     if (direction == BPF_INGRESS && skb->ifindex != 1) {
         if (uidRules & IIF_MATCH) {
             if (allowed_iif && skb->ifindex != allowed_iif) {
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 51ff5ec..0bb98f8 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -1300,6 +1300,17 @@
     }
 
     /**
+     * Removes the interface name from all entries.
+     * This mutates the original structure in place.
+     * @hide
+     */
+    public void clearInterfaces() {
+        for (int i = 0; i < size; i++) {
+            iface[i] = null;
+        }
+    }
+
+    /**
      * Only keep entries that match all specified filters.
      *
      * <p>This mutates the original structure in place. After this method is called,
diff --git a/framework/Android.bp b/framework/Android.bp
index d7de439..24d8cca 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,7 +64,6 @@
         ":framework-connectivity-sources",
         ":net-utils-framework-common-srcs",
         ":framework-connectivity-api-shared-srcs",
-        ":framework-connectivity-javastream-protos",
     ],
     aidl: {
         generate_get_transaction_name: true,
@@ -90,6 +89,7 @@
         "modules-utils-backgroundthread",
         "modules-utils-build",
         "modules-utils-preconditions",
+        "framework-connectivity-javastream-protos",
     ],
     libs: [
         "app-compat-annotations",
@@ -197,28 +197,16 @@
     visibility: ["//frameworks/base"],
 }
 
-gensrcs {
+java_library {
     name: "framework-connectivity-javastream-protos",
-    depfile: true,
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-javastream",
-        "soong_zip",
+    proto: {
+        type: "stream",
+    },
+    srcs: [":framework-connectivity-protos"],
+    installable: false,
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
     ],
-
-    cmd: "mkdir -p $(genDir)/$(in) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-javastream) " +
-        "  --dependency_out=$(depfile) " +
-        "  --javastream_out=$(genDir)/$(in) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in) " +
-        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
-    srcs: [
-        ":framework-connectivity-protos",
-    ],
-    output_extension: "srcjar",
 }
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index cd29185..ff6e45d 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
@@ -172,6 +173,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
@@ -341,11 +343,16 @@
     @GuardedBy("mStatsLock")
     private String mActiveIface;
 
-    /** Set of any ifaces associated with mobile networks since boot. */
+    /** Set of all ifaces currently associated with mobile networks. */
     private volatile String[] mMobileIfaces = new String[0];
 
-    /** Set of any ifaces associated with wifi networks since boot. */
-    private volatile String[] mWifiIfaces = new String[0];
+    /* A set of all interfaces that have ever been associated with mobile networks since boot. */
+    @GuardedBy("mStatsLock")
+    private final Set<String> mAllMobileIfacesSinceBoot = new ArraySet<>();
+
+    /* A set of all interfaces that have ever been associated with wifi networks since boot. */
+    @GuardedBy("mStatsLock")
+    private final Set<String> mAllWifiIfacesSinceBoot = new ArraySet<>();
 
     /** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
     @GuardedBy("mStatsLock")
@@ -1468,9 +1475,9 @@
         // We've been using pure XT stats long enough that we no longer need to
         // splice DEV and XT together.
         final NetworkStatsHistory history = internalGetHistoryForNetwork(template, flags, FIELD_ALL,
-                accessLevel, callingUid, start, end);
+                accessLevel, callingUid, Long.MIN_VALUE, Long.MAX_VALUE);
 
-        final long now = System.currentTimeMillis();
+        final long now = mClock.millis();
         final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
 
         final NetworkStats stats = new NetworkStats(end - start, 1);
@@ -1551,17 +1558,37 @@
         return dataLayer;
     }
 
+    private String[] getAllIfacesSinceBoot(int transport) {
+        synchronized (mStatsLock) {
+            final Set<String> ifaceSet;
+            if (transport == TRANSPORT_WIFI) {
+                ifaceSet = mAllWifiIfacesSinceBoot;
+            } else if (transport == TRANSPORT_CELLULAR) {
+                ifaceSet = mAllMobileIfacesSinceBoot;
+            } else {
+                throw new IllegalArgumentException("Invalid transport " + transport);
+            }
+
+            return ifaceSet.toArray(new String[0]);
+        }
+    }
+
     @Override
     public NetworkStats getUidStatsForTransport(int transport) {
         PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
-            final String[] relevantIfaces =
-                    transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+            final String[] ifaceArray = getAllIfacesSinceBoot(transport);
             // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
             // interfaces, so this is not useful, remove it.
             final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
-            return getNetworkStatsUidDetail(ifacesToQuery);
+                    mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
+            final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+            // Clear the interfaces of the stats before returning, so callers won't get this
+            // information. This is because no caller needs this information for now, and it
+            // makes it easier to change the implementation later by using the histories in the
+            // recorder.
+            stats.clearInterfaces();
+            return stats;
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
@@ -1570,11 +1597,6 @@
 
     @Override
     public String[] getMobileIfaces() {
-        // TODO (b/192758557): Remove debug log.
-        if (CollectionUtils.contains(mMobileIfaces, null)) {
-            throw new NullPointerException(
-                    "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
-        }
         return mMobileIfaces.clone();
     }
 
@@ -1942,7 +1964,6 @@
 
         final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
         final ArraySet<String> mobileIfaces = new ArraySet<>();
-        final ArraySet<String> wifiIfaces = new ArraySet<>();
         for (NetworkStateSnapshot snapshot : snapshots) {
             final int displayTransport =
                     getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
@@ -1987,9 +2008,12 @@
 
                 if (isMobile) {
                     mobileIfaces.add(baseIface);
+                    // If the interface name was present in the wifi set, the interface won't
+                    // be removed from it to prevent stats from getting rollback.
+                    mAllMobileIfacesSinceBoot.add(baseIface);
                 }
                 if (isWifi) {
-                    wifiIfaces.add(baseIface);
+                    mAllWifiIfacesSinceBoot.add(baseIface);
                 }
             }
 
@@ -2031,9 +2055,10 @@
                     findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
                     if (isMobile) {
                         mobileIfaces.add(iface);
+                        mAllMobileIfacesSinceBoot.add(iface);
                     }
                     if (isWifi) {
-                        wifiIfaces.add(iface);
+                        mAllWifiIfacesSinceBoot.add(iface);
                     }
 
                     mStatsFactory.noteStackedIface(iface, baseIface);
@@ -2042,16 +2067,6 @@
         }
 
         mMobileIfaces = mobileIfaces.toArray(new String[0]);
-        mWifiIfaces = wifiIfaces.toArray(new String[0]);
-        // TODO (b/192758557): Remove debug log.
-        if (CollectionUtils.contains(mMobileIfaces, null)) {
-            throw new NullPointerException(
-                    "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
-        }
-        if (CollectionUtils.contains(mWifiIfaces, null)) {
-            throw new NullPointerException(
-                    "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
-        }
     }
 
     private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -2516,6 +2531,22 @@
             }
             pw.decreaseIndent();
 
+            pw.println("All wifi interfaces:");
+            pw.increaseIndent();
+            for (String iface : mAllWifiIfacesSinceBoot) {
+                pw.print(iface + " ");
+            }
+            pw.println();
+            pw.decreaseIndent();
+
+            pw.println("All mobile interfaces:");
+            pw.increaseIndent();
+            for (String iface : mAllMobileIfacesSinceBoot) {
+                pw.print(iface + " ");
+            }
+            pw.println();
+            pw.decreaseIndent();
+
             // Get the top openSession callers
             final HashMap calls;
             synchronized (mOpenSessionCallsLock) {
diff --git a/service/Android.bp b/service/Android.bp
index 91b9d1c..45e43bc 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -198,11 +198,10 @@
     lint: { strict_updatability_linting: true },
 }
 
-java_library {
-    name: "service-connectivity",
+java_defaults {
+    name: "service-connectivity-defaults",
     sdk_version: "system_server_current",
     min_sdk_version: "30",
-    installable: true,
     // This library combines system server jars that have access to different bootclasspath jars.
     // Lower SDK service jars must not depend on higher SDK jars as that would let them
     // transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
@@ -224,6 +223,24 @@
     lint: { strict_updatability_linting: true },
 }
 
+// A special library created strictly for use by the tests as they need the
+// implementation library but that is not available when building from prebuilts.
+// Using a library with a different name to what is used by the prebuilts ensures
+// that this will never depend on the prebuilt.
+// Switching service-connectivity to a java_sdk_library would also have worked as
+// that has built in support for managing this but that is too big a change at this
+// point.
+java_library {
+    name: "service-connectivity-for-tests",
+    defaults: ["service-connectivity-defaults"],
+}
+
+java_library {
+    name: "service-connectivity",
+    defaults: ["service-connectivity-defaults"],
+    installable: true,
+}
+
 filegroup {
     name: "connectivity-jarjar-rules",
     srcs: ["jarjar-rules.txt"],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index c7223fc..4013d2e 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -118,6 +118,7 @@
 rule androidx.core.** com.android.server.nearby.@0
 rule androidx.versionedparcelable.** com.android.server.nearby.@0
 rule com.google.common.** com.android.server.nearby.@0
+rule android.support.v4.** com.android.server.nearby.@0
 
 # Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
 # TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 1aca633..2eabc26 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -68,6 +68,7 @@
 constexpr int TXBYTES = 0;
 
 #define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+#define ASSERT_INVALID(x) ASSERT_FALSE((x).isValid())
 
 class TrafficControllerTest : public ::testing::Test {
   protected:
@@ -76,6 +77,8 @@
     BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
     BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
     BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+    BpfMap<StatsKey, StatsValue> mFakeStatsMapB;  // makeTrafficControllerMapsInvalid only
+    BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap; ;  // makeTrafficControllerMapsInvalid only
     BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
     BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
     BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
@@ -356,6 +359,52 @@
         }
         return true;
     }
+
+    // Once called, the maps of TrafficController can't recover to valid maps which initialized
+    // in SetUp().
+    void makeTrafficControllerMapsInvalid() {
+        constexpr char INVALID_PATH[] = "invalid";
+
+        mFakeCookieTagMap.init(INVALID_PATH);
+        mTc.mCookieTagMap = mFakeCookieTagMap;
+        ASSERT_INVALID(mTc.mCookieTagMap);
+
+        mFakeAppUidStatsMap.init(INVALID_PATH);
+        mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
+        ASSERT_INVALID(mTc.mAppUidStatsMap);
+
+        mFakeStatsMapA.init(INVALID_PATH);
+        mTc.mStatsMapA = mFakeStatsMapA;
+        ASSERT_INVALID(mTc.mStatsMapA);
+
+        mFakeStatsMapB.init(INVALID_PATH);
+        mTc.mStatsMapB = mFakeStatsMapB;
+        ASSERT_INVALID(mTc.mStatsMapB);
+
+        mFakeIfaceStatsMap.init(INVALID_PATH);
+        mTc.mIfaceStatsMap = mFakeIfaceStatsMap;
+        ASSERT_INVALID(mTc.mIfaceStatsMap);
+
+        mFakeConfigurationMap.init(INVALID_PATH);
+        mTc.mConfigurationMap = mFakeConfigurationMap;
+        ASSERT_INVALID(mTc.mConfigurationMap);
+
+        mFakeUidOwnerMap.init(INVALID_PATH);
+        mTc.mUidOwnerMap = mFakeUidOwnerMap;
+        ASSERT_INVALID(mTc.mUidOwnerMap);
+
+        mFakeUidPermissionMap.init(INVALID_PATH);
+        mTc.mUidPermissionMap = mFakeUidPermissionMap;
+        ASSERT_INVALID(mTc.mUidPermissionMap);
+
+        mFakeUidCounterSetMap.init(INVALID_PATH);
+        mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+        ASSERT_INVALID(mTc.mUidCounterSetMap);
+
+        mFakeIfaceIndexNameMap.init(INVALID_PATH);
+        mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+        ASSERT_INVALID(mTc.mIfaceIndexNameMap);
+    }
 };
 
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -790,6 +839,71 @@
     EXPECT_TRUE(expectDumpsysContains(expectedLines));
 }
 
+TEST_F(TrafficControllerTest, dumpsysInvalidMaps) {
+    makeTrafficControllerMapsInvalid();
+
+    const std::string kErrIterate = "print end with error: Get firstKey map -1 failed: "
+            "Bad file descriptor";
+    const std::string kErrReadRulesConfig = "read ownerMatch configure failed with error: "
+            "Read value of map -1 failed: Bad file descriptor";
+    const std::string kErrReadStatsMapConfig = "read stats map configure failed with error: "
+            "Read value of map -1 failed: Bad file descriptor";
+
+    std::vector<std::string> expectedLines = {
+        fmt::format("mCookieTagMap {}", kErrIterate),
+        fmt::format("mUidCounterSetMap {}", kErrIterate),
+        fmt::format("mAppUidStatsMap {}", kErrIterate),
+        fmt::format("mStatsMapA {}", kErrIterate),
+        fmt::format("mStatsMapB {}", kErrIterate),
+        fmt::format("mIfaceIndexNameMap {}", kErrIterate),
+        fmt::format("mIfaceStatsMap {}", kErrIterate),
+        fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
+        fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
+        fmt::format("mUidOwnerMap {}", kErrIterate),
+        fmt::format("mUidPermissionMap {}", kErrIterate)};
+    EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, uidMatchTypeToString) {
+    // NO_MATCH(0) can't be verified because match type flag is added by OR operator.
+    // See TrafficController::addRule()
+    static const struct TestConfig {
+        UidOwnerMatchType uidOwnerMatchType;
+        std::string expected;
+    } testConfigs[] = {
+            // clang-format off
+            {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+            {DOZABLE_MATCH, "DOZABLE_MATCH"},
+            {STANDBY_MATCH, "STANDBY_MATCH"},
+            {POWERSAVE_MATCH, "POWERSAVE_MATCH"},
+            {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+            {RESTRICTED_MATCH, "RESTRICTED_MATCH"},
+            {LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"},
+            {IIF_MATCH, "IIF_MATCH"},
+            {LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"},
+            {OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"},
+            {OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"},
+            {OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"},
+            // clang-format on
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.uidOwnerMatchType,
+                     config.expected));
+
+        // Test private function uidMatchTypeToString() via dumpsys.
+        ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+                                            TrafficController::IptOpInsert)));
+        std::vector<std::string> expectedLines;
+        expectedLines.emplace_back(fmt::format("{}  {}", TEST_UID, config.expected));
+        EXPECT_TRUE(expectDumpsysContains(expectedLines));
+
+        // Clean up the stubs.
+        ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+                                            TrafficController::IptOpDelete)));
+    }
+}
+
 TEST_F(TrafficControllerTest, getFirewallType) {
     static const struct TestConfig {
         ChildChain childChain;
@@ -805,6 +919,7 @@
             {LOCKDOWN, DENYLIST},
             {OEM_DENY_1, DENYLIST},
             {OEM_DENY_2, DENYLIST},
+            {OEM_DENY_3, DENYLIST},
             {INVALID_CHAIN, DENYLIST},
             // clang-format on
     };
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 8cefd47..498cf63 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -498,6 +498,31 @@
         }
     }
 
+    private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
+            ParcelFileDescriptor writeSock6) {
+        if (tunFd != null) {
+            try {
+                tunFd.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close tun file descriptor " + e);
+            }
+        }
+        if (readSock6 != null) {
+            try {
+                readSock6.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close read socket " + e);
+            }
+        }
+        if (writeSock6 != null) {
+            try {
+                writeSock6.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close write socket " + e);
+            }
+        }
+    }
+
     /**
      * Start clatd for a given interface and NAT64 prefix.
      */
@@ -546,8 +571,15 @@
 
         // [3] Open, configure and bring up the tun interface.
         // Create the v4-... tun interface.
+
+        // Initialize all required file descriptors with null pointer. This makes the following
+        // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
+        // if any valid ones, in error handling.
+        ParcelFileDescriptor tunFd = null;
+        ParcelFileDescriptor readSock6 = null;
+        ParcelFileDescriptor writeSock6 = null;
+
         final String tunIface = CLAT_PREFIX + iface;
-        final ParcelFileDescriptor tunFd;
         try {
             tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
         } catch (IOException e) {
@@ -556,7 +588,7 @@
 
         final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
         if (tunIfIndex == INVALID_IFINDEX) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Fail to get interface index for interface " + tunIface);
         }
 
@@ -564,7 +596,6 @@
         try {
             mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
             Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
         }
 
@@ -575,19 +606,17 @@
             detectedMtu = mDeps.detectMtu(pfx96Str,
                 ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
         } catch (IOException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
         }
         final int mtu = adjustMtu(detectedMtu);
         Log.i(TAG, "ipv4 mtu is " + mtu);
 
-        // TODO: add setIptablesDropRule
-
         // Config tun interface mtu, address and bring up.
         try {
             mNetd.interfaceSetMtu(tunIface, mtu);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
         }
         final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
@@ -599,14 +628,13 @@
         try {
             mNetd.interfaceSetCfg(ifConfig);
         } catch (RemoteException | ServiceSpecificException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
                     + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
         }
 
         // [4] Open and configure local 464xlat read/write sockets.
         // Opens a packet socket to receive IPv6 packets in clatd.
-        final ParcelFileDescriptor readSock6;
         try {
             // Use a JNI call to get native file descriptor instead of Os.socket() because we would
             // like to use ParcelFileDescriptor to manage file descriptor. But ctor
@@ -614,27 +642,23 @@
             // descriptor to initialize ParcelFileDescriptor object instead.
             readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
         } catch (IOException e) {
-            tunFd.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Open packet socket failed: " + e);
         }
 
         // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
-        final ParcelFileDescriptor writeSock6;
         try {
             // Use a JNI call to get native file descriptor instead of Os.socket(). See above
             // reason why we use jniOpenPacketSocket6().
             writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Open raw socket failed: " + e);
         }
 
         final int ifIndex = mDeps.getInterfaceIndex(iface);
         if (ifIndex == INVALID_IFINDEX) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("Fail to get interface index for interface " + iface);
         }
 
@@ -642,9 +666,7 @@
         try {
             mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("add anycast sockopt failed: " + e);
         }
 
@@ -653,9 +675,7 @@
         try {
             cookie = mDeps.tagSocketAsClat(writeSock6.getFileDescriptor());
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("tag raw socket failed: " + e);
         }
 
@@ -663,9 +683,7 @@
         try {
             mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
         } catch (IOException e) {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            maybeCleanUp(tunFd, readSock6, writeSock6);
             throw new IOException("configure packet socket failed: " + e);
         }
 
@@ -679,9 +697,9 @@
             mDeps.untagSocket(cookie);
             throw new IOException("Error start clatd on " + iface + ": " + e);
         } finally {
-            tunFd.close();
-            readSock6.close();
-            writeSock6.close();
+            // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+            // Close these file descriptor stubs which are unused anymore.
+            maybeCleanUp(tunFd, readSock6, writeSock6);
         }
 
         // [6] Initialize and store clatd tracker object.
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 581ee22..9ed2bb3 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,7 +20,6 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
 import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
 
@@ -53,6 +52,7 @@
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.net.Inet4Address;
@@ -68,11 +68,13 @@
 @SmallTest
 @ConnectivityModuleTest
 public class LinkPropertiesTest {
+    // Use a RuleChain to explicitly specify the order of rules. DevSdkIgnoreRule must run before
+    // PlatformCompatChange rule, because otherwise tests with that should be skipped when targeting
+    // target SDK 33 will still attempt to override compat changes (which on user builds will crash)
+    // before being skipped.
     @Rule
-    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
-    @Rule
-    public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+    public final RuleChain chain = RuleChain.outerRule(
+            new DevSdkIgnoreRule()).around(new PlatformCompatChangeRule());
 
     private static final InetAddress ADDRV4 = address("75.208.6.1");
     private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
@@ -1262,7 +1264,8 @@
         assertFalse(lp.hasIpv4UnreachableDefaultRoute());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testHasExcludeRoute() {
         LinkProperties lp = new LinkProperties();
@@ -1274,7 +1277,8 @@
         assertTrue(lp.hasExcludeRoute());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testRouteAddWithSameKey() throws Exception {
         LinkProperties lp = new LinkProperties();
@@ -1291,7 +1295,8 @@
         assertEquals(2, lp.getRoutes().size());
     }
 
-    @Test @IgnoreUpTo(SC_V2)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testExcludedRoutesEnabled() {
         final LinkProperties lp = new LinkProperties();
@@ -1307,8 +1312,8 @@
         assertEquals(3, lp.getRoutes().size());
     }
 
-    @Test @IgnoreUpTo(SC_V2)
-    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden on T or above")
+    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabled() {
         final LinkProperties lp = new LinkProperties();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
index 098f295..10775d0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
@@ -75,6 +75,8 @@
     @RequiredProperties({DOZE_MODE})
     public void testStartActivity_doze() throws Exception {
         setDozeMode(true);
+        // TODO (235284115): We need to turn on Doze every time before starting
+        // the activity.
         assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
     }
 
@@ -83,6 +85,8 @@
     public void testStartActivity_appStandby() throws Exception {
         turnBatteryOn();
         setAppIdle(true);
+        // TODO (235284115): We need to put the app into app standby mode every
+        // time before starting the activity.
         assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
     }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index dc67c70..dd8b523 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -378,10 +378,6 @@
         if (mNetwork == null) {
             fail("VPN did not become available after " + TIMEOUT_MS + "ms");
         }
-
-        // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
-        // configured. Give the system some time to do so. http://b/18436087 .
-        try { Thread.sleep(3000); } catch(InterruptedException e) {}
     }
 
     private void stopVpn() {
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
index 3387fd7..cfd3130 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net;
 
+import android.platform.test.annotations.FlakyTest;
+
 public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
     private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
     @Override
@@ -41,6 +43,7 @@
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
     }
 
+    @FlakyTest(bugId = 231440256)
     public void testStartActivity_doze() throws Exception {
         runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
     }
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 6dfadc7..bd1b74a 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,13 +26,8 @@
 import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
-import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
 import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
 
-import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -73,19 +68,14 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.TestCarrierConfigReceiver;
-
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -94,9 +84,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TetheringManagerTest {
 
-    @Rule
-    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
-
     private Context mContext;
 
     private ConnectivityManager mCm;
@@ -405,7 +392,7 @@
         // Override carrier config to ignore entitlement check.
         final PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, false);
-        overrideCarrierConfig(bundle, CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+        overrideCarrierConfig(bundle);
 
         // Verify that requestLatestTetheringEntitlementResult() can get entitlement
         // result TETHER_ERROR_NO_ERROR due to provisioning bypassed.
@@ -413,112 +400,14 @@
                 TETHERING_WIFI, false, c -> c.run(), listener), TETHER_ERROR_NO_ERROR);
 
         // Reset carrier config.
-        overrideCarrierConfig(null, "");
+        overrideCarrierConfig(null);
     }
 
-    @Test
-    @IgnoreUpTo(SC_V2)
-    public void testEnableTethering_carrierUnsupported_noTetheringActive() throws Exception {
-        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
-        final TestTetheringEventCallback tetherEventCallback =
-                mCtsTetheringUtils.registerTetheringEventCallback();
-        boolean previousWifiEnabledState = false;
-        try {
-            tetherEventCallback.assumeWifiTetheringSupported(mContext);
-            // Avoid device connected to Wifi network.
-            previousWifiEnabledState = ensureCurrentNetworkIsCellular();
-            final PersistableBundle bundle = new PersistableBundle();
-            bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
-            // Override carrier config to make carrier not support.
-            overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
-            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
-            mCtsTetheringUtils.expectSoftApDisabled();
-            tetherEventCallback.expectNoTetheringActive();
-        } finally {
-            overrideCarrierConfig(null, "");
-            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
-            if (previousWifiEnabledState) {
-                mCtsNetUtils.connectToWifi();
-            }
-        }
-    }
-
-    @Test
-    @IgnoreUpTo(SC_V2)
-    public void testEnableTethering_carrierUnsupportByConfigChange_noTetheringActive()
-            throws Exception {
-        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
-        final TestTetheringEventCallback tetherEventCallback =
-                mCtsTetheringUtils.registerTetheringEventCallback();
-        boolean previousWifiEnabledState = false;
-        try {
-            tetherEventCallback.assumeWifiTetheringSupported(mContext);
-            // Avoid device connected to Wifi network.
-            previousWifiEnabledState = ensureCurrentNetworkIsCellular();
-            mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
-            final PersistableBundle bundle = new PersistableBundle();
-            bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
-            // Override carrier config to make carrier not support.
-            overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
-            mCtsTetheringUtils.expectSoftApDisabled();
-            tetherEventCallback.expectNoTetheringActive();
-        } finally {
-            overrideCarrierConfig(null, "");
-            mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
-            if (previousWifiEnabledState) {
-                mCtsNetUtils.connectToWifi();
-            }
-        }
-    }
-
-    @Test
-    @IgnoreUpTo(SC_V2)
-    public void testRequestLatestEntitlementResult_carrierUnsupported() throws Exception {
-        assumeTrue(mTM.isTetheringSupported());
-        assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
-
-        final PersistableBundle bundle = new PersistableBundle();
-        bundle.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
-        try {
-            // Override carrier config to make carrier not support.
-            overrideCarrierConfig(bundle, KEY_CARRIER_SUPPORTS_TETHERING_BOOL);
-
-            // Verify that requestLatestTetheringEntitlementResult() can get entitlement
-            // result TETHER_ERROR_PROVISIONING_FAILED due to carrier unsupported
-            assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
-                    TETHERING_WIFI,
-                    false,
-                    c -> c.run(),
-                    listener),
-                    TETHER_ERROR_PROVISIONING_FAILED);
-        } finally {
-            // Reset carrier config.
-            overrideCarrierConfig(null, "");
-        }
-    }
-
-    private void overrideCarrierConfig(PersistableBundle bundle, String configName)
-            throws Exception {
-        final int timeoutMs = 5_000;
-        final int currentSubId = SubscriptionManager.getDefaultSubscriptionId();
-        TestCarrierConfigReceiver configListener =
-                new TestCarrierConfigReceiver(mContext, currentSubId, timeoutMs, bundle,
-                        (configs) -> {
-                            if (bundle == null) {
-                                // This is to restore carrier config and means no need to do match.
-                                return true;
-                            }
-                            boolean requestConfigValue = bundle.getBoolean(configName);
-                            boolean receiveConfigValue = configs.getBoolean(configName);
-                            return Objects.equals(receiveConfigValue, requestConfigValue);
-                        });
-        configListener.overrideCarrierConfigForTest();
+    private void overrideCarrierConfig(PersistableBundle bundle) {
+        final CarrierConfigManager configManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        final int subId = SubscriptionManager.getDefaultSubscriptionId();
+        configManager.overrideConfig(subId, bundle);
     }
 
     @Test
@@ -532,8 +421,30 @@
         try {
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
             tetherEventCallback.expectNoTetheringActive();
-            // Avoid device connected to Wifi network.
-            previousWifiEnabledState = ensureCurrentNetworkIsCellular();
+
+            previousWifiEnabledState = mWm.isWifiEnabled();
+            if (previousWifiEnabledState) {
+                mCtsNetUtils.ensureWifiDisconnected(null);
+            }
+
+            final TestNetworkCallback networkCallback = new TestNetworkCallback();
+            Network activeNetwork = null;
+            try {
+                mCm.registerDefaultNetworkCallback(networkCallback);
+                activeNetwork = networkCallback.waitForAvailable();
+            } finally {
+                mCm.unregisterNetworkCallback(networkCallback);
+            }
+
+            assertNotNull("No active network. Please ensure the device has working mobile data.",
+                    activeNetwork);
+            final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
+
+            // If active nework is ETHERNET, tethering may not use cell network as upstream.
+            assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
+
+            assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
+
             mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
 
             final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
@@ -553,36 +464,4 @@
             }
         }
     }
-
-    /**
-     * Make sure current network is cellular data.
-     * @return true Previous Wifi state is enabled, false is disabled.
-     */
-    private boolean ensureCurrentNetworkIsCellular() throws Exception {
-        final boolean previousWifiEnabledState = mWm.isWifiEnabled();
-        if (previousWifiEnabledState) {
-            mCtsNetUtils.ensureWifiDisconnected(null);
-        }
-
-        final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        Network activeNetwork = null;
-        try {
-            mCm.registerDefaultNetworkCallback(networkCallback);
-            activeNetwork = networkCallback.waitForAvailable();
-        } finally {
-            mCm.unregisterNetworkCallback(networkCallback);
-        }
-
-        assertNotNull("No active network. Please ensure the device has working mobile data.",
-                activeNetwork);
-
-        final NetworkCapabilities activeNetCap = mCm.getNetworkCapabilities(activeNetwork);
-
-        // If active nework is ETHERNET, tethering may not use cell network as upstream.
-        assumeFalse(activeNetCap.hasTransport(TRANSPORT_ETHERNET));
-
-        assertTrue(activeNetCap.hasTransport(TRANSPORT_CELLULAR));
-
-        return previousWifiEnabledState;
-    }
 }
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 97c1265..b3684ac 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -71,7 +71,7 @@
         "net-tests-utils",
     ],
     libs: [
-        "service-connectivity",
+        "service-connectivity-for-tests",
         "services.core",
         "services.net",
     ],
diff --git a/tests/smoketest/Android.bp b/tests/smoketest/Android.bp
index df8ab74..4ab24fc 100644
--- a/tests/smoketest/Android.bp
+++ b/tests/smoketest/Android.bp
@@ -22,6 +22,6 @@
     static_libs: [
         "androidx.test.rules",
         "mockito-target-minus-junit4",
-        "service-connectivity",
+        "service-connectivity-for-tests",
     ],
 }
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 3047a16..8dfe924 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -556,14 +557,28 @@
                 () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
     }
 
-    private void checkNotStartClat(final TestDependencies deps, final boolean verifyTunFd,
-            final boolean verifyPacketSockFd, final boolean verifyRawSockFd) throws Exception {
+    private void checkNotStartClat(final TestDependencies deps, final boolean needToCloseTunFd,
+            final boolean needToClosePacketSockFd, final boolean needToCloseRawSockFd)
+            throws Exception {
         // [1] Expect that modified TestDependencies can't start clatd.
+        // Use precise check to make sure that there is no unexpected file descriptor closing.
         clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
         assertNotStartClat(deps);
-        if (verifyTunFd) verify(TUN_PFD).close();
-        if (verifyPacketSockFd) verify(PACKET_SOCK_PFD).close();
-        if (verifyRawSockFd) verify(RAW_SOCK_PFD).close();
+        if (needToCloseTunFd) {
+            verify(TUN_PFD).close();
+        } else {
+            verify(TUN_PFD, never()).close();
+        }
+        if (needToClosePacketSockFd) {
+            verify(PACKET_SOCK_PFD).close();
+        } else {
+            verify(PACKET_SOCK_PFD, never()).close();
+        }
+        if (needToCloseRawSockFd) {
+            verify(RAW_SOCK_PFD).close();
+        } else {
+            verify(RAW_SOCK_PFD, never()).close();
+        }
 
         // [2] Expect that unmodified TestDependencies can start clatd.
         // Used to make sure that the above modified TestDependencies has really broken the
@@ -582,8 +597,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -595,8 +610,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -607,8 +622,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), false /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -620,8 +635,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -632,8 +647,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                false /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -644,8 +659,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, false /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -657,8 +672,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -669,8 +684,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -682,8 +697,8 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 
     @Test
@@ -697,7 +712,7 @@
                 throw new IOException();
             }
         }
-        checkNotStartClat(new FailureDependencies(), true /* verifyTunFd */,
-                true /* verifyPacketSockFd */, true /* verifyRawSockFd */);
+        checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+                true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index ec51537..d1bf40e 100644
--- a/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -27,6 +27,7 @@
 
 import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
 import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -184,7 +185,7 @@
                 (int) setting);
     }
 
-    private void testGetMultipathPreference(
+    private void prepareGetMultipathPreferenceTest(
             long usedBytesToday, long subscriptionQuota, long policyWarning, long policyLimit,
             long defaultGlobalSetting, long defaultResSetting, boolean roaming) {
 
@@ -286,7 +287,7 @@
 
     @Test
     public void testGetMultipathPreference_SubscriptionQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
                 DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
@@ -301,7 +302,7 @@
 
     @Test
     public void testGetMultipathPreference_UserWarningQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 // Remaining days are 29 days from Apr. 2nd to May 1st.
@@ -320,7 +321,7 @@
 
     @Test
     public void testGetMultipathPreference_SnoozedWarningQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 POLICY_SNOOZED /* policyWarning */,
@@ -339,7 +340,7 @@
 
     @Test
     public void testGetMultipathPreference_SnoozedBothQuota() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(7) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 // 29 days from Apr. 2nd to May 1st
@@ -356,7 +357,7 @@
 
     @Test
     public void testGetMultipathPreference_SettingChanged() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 WARNING_DISABLED,
@@ -381,7 +382,7 @@
 
     @Test
     public void testGetMultipathPreference_ResourceChanged() {
-        testGetMultipathPreference(
+        prepareGetMultipathPreferenceTest(
                 DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
                 OPPORTUNISTIC_QUOTA_UNKNOWN,
                 WARNING_DISABLED,
@@ -404,4 +405,45 @@
         verify(mStatsManager, times(1)).registerUsageCallback(
                 any(), eq(DataUnit.MEGABYTES.toBytes(14)), any(), any());
     }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testOnThresholdReached() {
+        prepareGetMultipathPreferenceTest(
+                DataUnit.MEGABYTES.toBytes(2) /* usedBytesToday */,
+                DataUnit.MEGABYTES.toBytes(14) /* subscriptionQuota */,
+                DataUnit.MEGABYTES.toBytes(100) /* policyWarning */,
+                LIMIT_DISABLED,
+                DataUnit.MEGABYTES.toBytes(12) /* defaultGlobalSetting */,
+                2_500_000 /* defaultResSetting */,
+                false /* roaming */);
+
+        final ArgumentCaptor<NetworkStatsManager.UsageCallback> usageCallbackCaptor =
+                ArgumentCaptor.forClass(NetworkStatsManager.UsageCallback.class);
+        final ArgumentCaptor<NetworkTemplate> networkTemplateCaptor =
+                ArgumentCaptor.forClass(NetworkTemplate.class);
+        // Verify the callback is registered with quota - used = 14 - 2 = 12MB.
+        verify(mStatsManager, times(1)).registerUsageCallback(
+                networkTemplateCaptor.capture(), eq(DataUnit.MEGABYTES.toBytes(12)), any(),
+                usageCallbackCaptor.capture());
+
+        // Capture arguments for later use.
+        final NetworkStatsManager.UsageCallback usageCallback = usageCallbackCaptor.getValue();
+        final NetworkTemplate template = networkTemplateCaptor.getValue();
+        assertNotNull(usageCallback);
+        assertNotNull(template);
+
+        // Decrease quota from 14 to 11, and trigger the event.
+        // TODO: Mock daily and monthly used bytes instead of changing subscription to simulate
+        //  remaining quota changed.
+        when(mNPMI.getSubscriptionOpportunisticQuota(TEST_NETWORK, QUOTA_TYPE_MULTIPATH))
+                .thenReturn(DataUnit.MEGABYTES.toBytes(11));
+        usageCallback.onThresholdReached(template);
+
+        // Callback must have been re-registered with new remaining quota = 11 - 2 = 9MB.
+        verify(mStatsManager, times(1))
+                .unregisterUsageCallback(eq(usageCallback));
+        verify(mStatsManager, times(1)).registerUsageCallback(
+                eq(template), eq(DataUnit.MEGABYTES.toBytes(9)), any(), eq(usageCallback));
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index eb35469..cdfa190 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1315,7 +1315,7 @@
                 config -> Arrays.asList(config.flags).contains(flag)));
     }
 
-    private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+    private void doTestPlatformVpnWithException(IkeException exception,
             String category, int errorType, int errorCode) throws Exception {
         final ArgumentCaptor<IkeSessionCallback> captor =
                 ArgumentCaptor.forClass(IkeSessionCallback.class);
@@ -1333,6 +1333,7 @@
         // state
         verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
                 .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+        reset(mIkev2SessionCreator);
         final IkeSessionCallback ikeCb = captor.getValue();
         ikeCb.onClosedWithException(exception);
 
@@ -1342,6 +1343,23 @@
         if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
             verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
                     .unregisterNetworkCallback(eq(cb));
+        } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) {
+            // To prevent spending much time to test the retry function, only retry 2 times here.
+            int retryIndex = 0;
+            verify(mIkev2SessionCreator,
+                    timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+                            + TEST_TIMEOUT_MS))
+                    .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+
+            // Capture a new IkeSessionCallback to get the latest token.
+            reset(mIkev2SessionCreator);
+            final IkeSessionCallback ikeCb2 = captor.getValue();
+            ikeCb2.onClosedWithException(exception);
+            verify(mIkev2SessionCreator,
+                    timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+                            + TEST_TIMEOUT_MS))
+                    .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+            reset(mIkev2SessionCreator);
         }
     }
 
@@ -1350,7 +1368,7 @@
         final IkeProtocolException exception = mock(IkeProtocolException.class);
         final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
         when(exception.getErrorType()).thenReturn(errorCode);
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
                 errorCode);
     }
@@ -1360,7 +1378,7 @@
         final IkeProtocolException exception = mock(IkeProtocolException.class);
         final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
         when(exception.getErrorType()).thenReturn(errorCode);
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
     }
 
@@ -1370,7 +1388,7 @@
         final UnknownHostException unknownHostException = new UnknownHostException();
         final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
         when(exception.getCause()).thenReturn(unknownHostException);
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
                 errorCode);
     }
@@ -1382,7 +1400,7 @@
                 new IkeTimeoutException("IkeTimeoutException");
         final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
         when(exception.getCause()).thenReturn(ikeTimeoutException);
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
                 errorCode);
     }
@@ -1391,7 +1409,7 @@
     public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
         final IkeNetworkLostException exception = new IkeNetworkLostException(
                 new Network(100));
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
                 VpnManager.ERROR_CODE_NETWORK_LOST);
     }
@@ -1402,7 +1420,7 @@
         final IOException ioException = new IOException();
         final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
         when(exception.getCause()).thenReturn(ioException);
-        setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+        doTestPlatformVpnWithException(exception,
                 VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
                 errorCode);
     }
@@ -1694,6 +1712,11 @@
         public DeviceIdleInternal getDeviceIdleInternal() {
             return mDeviceIdleInternal;
         }
+
+        public long getNextRetryDelaySeconds(int retryCount) {
+            // Simply return retryCount as the delay seconds for retrying.
+            return retryCount;
+        }
     }
 
     /**
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index f1820b3..4fbbc75 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -140,16 +140,6 @@
 import com.android.testutils.TestBpfMap;
 import com.android.testutils.TestableNetworkStatsProviderBinder;
 
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Clock;
-import java.time.ZoneOffset;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import libcore.testing.io.TestIoUtils;
 
 import org.junit.After;
@@ -161,6 +151,19 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /**
  * Tests for {@link NetworkStatsService}.
  *
@@ -1105,6 +1108,40 @@
     }
 
     @Test
+    public void testGetLatestSummary() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{buildWifiState()};
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
+
+        // Increase arbitrary time which does not align to the bucket edge, create some traffic.
+        incrementCurrentTime(1751000L);
+        NetworkStats.Entry entry = new NetworkStats.Entry(
+                TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 50L, 5L, 51L, 1L, 3L);
+        expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1).insertEntry(entry));
+        expectNetworkStatsUidDetail(buildEmptyStats());
+        forcePollAndWaitForIdle();
+
+        // Verify the mocked stats is returned by querying with the range of the latest bucket.
+        final ZonedDateTime end =
+                ZonedDateTime.ofInstant(mClock.instant(), ZoneId.systemDefault());
+        final ZonedDateTime start = end.truncatedTo(ChronoUnit.HOURS);
+        NetworkStats stats = mSession.getSummaryForNetwork(buildTemplateWifi(TEST_WIFI_NETWORK_KEY),
+                start.toInstant().toEpochMilli(), end.toInstant().toEpochMilli());
+        assertEquals(1, stats.size());
+        assertValues(stats, IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, 50L, 5L, 51L, 1L, 3L);
+
+        // For getHistoryIntervalForNetwork, only includes buckets that atomically occur in
+        // the inclusive time range, instead of including the latest bucket. This behavior is
+        // already documented publicly, refer to {@link NetworkStatsManager#queryDetails}.
+    }
+
+    @Test
     public void testUidStatsForTransport() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();
@@ -1135,9 +1172,12 @@
 
         assertEquals(3, stats.size());
         entry1.operations = 1;
+        entry1.iface = null;
         assertEquals(entry1, stats.getValues(0, null));
         entry2.operations = 1;
+        entry2.iface = null;
         assertEquals(entry2, stats.getValues(1, null));
+        entry3.iface = null;
         assertEquals(entry3, stats.getValues(2, null));
     }