Merge "Revert "Revert "Test eventuallyExpect"""
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index face72c..89c70a9 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -20,6 +20,8 @@
 import static android.net.metrics.IpReachabilityEvent.NUD_FAILED_ORGANIC;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST;
 import static android.net.metrics.IpReachabilityEvent.PROVISIONING_LOST_ORGANIC;
+import static android.net.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -43,8 +45,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.DeviceConfigUtils;
 import com.android.networkstack.R;
 
 import java.io.PrintWriter;
@@ -143,6 +149,8 @@
     protected static final int MIN_NUD_SOLICIT_NUM = 5;
     protected static final int MAX_NUD_SOLICIT_INTERVAL_MS = 1000;
     protected static final int MIN_NUD_SOLICIT_INTERVAL_MS = 750;
+    protected static final int NUD_MCAST_RESOLICIT_NUM = 3;
+    private static final int INVALID_NUD_MCAST_RESOLICIT_NUM = -1;
 
     public interface Callback {
         /**
@@ -161,6 +169,7 @@
     interface Dependencies {
         void acquireWakeLock(long durationMs);
         IpNeighborMonitor makeIpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb);
+        boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled);
 
         static Dependencies makeDefault(Context context, String iface) {
             final String lockName = TAG + "." + iface;
@@ -176,6 +185,12 @@
                         NeighborEventConsumer cb) {
                     return new IpNeighborMonitor(h, log, cb);
                 }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
+                            defaultEnabled);
+                }
             };
         }
     }
@@ -183,7 +198,6 @@
     private final InterfaceParams mInterfaceParams;
     private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
-    private final Callback mCallback;
     private final Dependencies mDependencies;
     private final boolean mUsingMultinetworkPolicyTracker;
     private final ConnectivityManager mCm;
@@ -196,6 +210,8 @@
     private volatile long mLastProbeTimeMs;
     private int mNumSolicits;
     private int mInterSolicitIntervalMs;
+    @NonNull
+    private final Callback mCallback;
 
     public IpReachabilityMonitor(
             Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
@@ -225,7 +241,10 @@
         // In case the overylaid parameters specify an invalid configuration, set the parameters
         // to the hardcoded defaults first, then set them to the values used in the steady state.
         try {
-            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS);
+            int numResolicits = isMulticastResolicitEnabled()
+                    ? NUD_MCAST_RESOLICIT_NUM
+                    : INVALID_NUD_MCAST_RESOLICIT_NUM;
+            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
         } catch (Exception e) {
             Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
         }
@@ -241,10 +260,12 @@
                     // TODO: Consider what to do with other states that are not within
                     // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
                     if (event.nudState == StructNdMsg.NUD_FAILED) {
+                        // After both unicast probe and multicast probe(if mcast_resolicit is not 0)
+                        // attempts fail, trigger the neighbor lost event and disconnect.
                         mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
                         handleNeighborLost(event);
                     } else if (event.nudState == StructNdMsg.NUD_REACHABLE) {
-                        maybeRestoreNeighborParameters();
+                        handleNeighborReachable(prev, event);
                     }
                 });
         mIpNeighborMonitor.start();
@@ -296,6 +317,26 @@
         return false;
     }
 
+    private boolean hasDefaultRouterNeighborMacAddressChanged(
+            @Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) {
+        if (prev == null || !isNeighborDefaultRouter(event)) return false;
+        return !event.macAddr.equals(prev.macAddr);
+    }
+
+    private boolean isNeighborDefaultRouter(@NonNull final NeighborEvent event) {
+        // For the IPv6 link-local scoped address, equals() works because the NeighborEvent.ip
+        // doesn't have a scope id and Inet6Address#equals doesn't consider scope id neither.
+        for (RouteInfo route : mLinkProperties.getRoutes()) {
+            if (route.isDefaultRoute() && event.ip.equals(route.getGateway())) return true;
+        }
+        return false;
+    }
+
+    private boolean isMulticastResolicitEnabled() {
+        return mDependencies.isFeatureEnabled(mContext, IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                false /* defaultEnabled */);
+    }
+
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -333,6 +374,24 @@
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
+    private void handleNeighborReachable(@Nullable final NeighborEvent prev,
+            @NonNull final NeighborEvent event) {
+        if (isMulticastResolicitEnabled()
+                && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+            // This implies device has confirmed the neighbor's reachability from
+            // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
+            // address hasn't changed is required. If Mac address does change, then
+            // trigger a new neighbor lost event and disconnect.
+            final String logMsg = "ALERT neighbor: " + event.ip
+                    + " MAC address changed from: " + prev.macAddr
+                    + " to: " + event.macAddr;
+            mLog.w(logMsg);
+            mCallback.notifyLost(event.ip, logMsg);
+            return;
+        }
+        maybeRestoreNeighborParameters();
+    }
+
     private void handleNeighborLost(NeighborEvent event) {
         final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
 
@@ -370,11 +429,9 @@
         if (lostProvisioning) {
             final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
-            if (mCallback != null) {
-                // TODO: remove |ip| when the callback signature no longer has
-                // an InetAddress argument.
-                mCallback.notifyLost(ip, logMsg);
-            }
+            // TODO: remove |ip| when the callback signature no longer has
+            // an InetAddress argument.
+            mCallback.notifyLost(ip, logMsg);
         }
         logNudFailed(lostProvisioning);
     }
@@ -450,6 +507,12 @@
 
     private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs)
             throws RemoteException, IllegalArgumentException {
+        // Do not set mcast_resolicit param by default.
+        setNeighborParameters(numSolicits, interSolicitIntervalMs, INVALID_NUD_MCAST_RESOLICIT_NUM);
+    }
+
+    private void setNeighborParameters(int numSolicits, int interSolicitIntervalMs,
+            int numResolicits) throws RemoteException, IllegalArgumentException {
         Preconditions.checkArgument(numSolicits >= MIN_NUD_SOLICIT_NUM,
                 "numSolicits must be at least " + MIN_NUD_SOLICIT_NUM);
         Preconditions.checkArgument(numSolicits <= MAX_NUD_SOLICIT_NUM,
@@ -464,6 +527,10 @@
                     Integer.toString(interSolicitIntervalMs));
             mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "ucast_solicit",
                     Integer.toString(numSolicits));
+            if (numResolicits != INVALID_NUD_MCAST_RESOLICIT_NUM) {
+                mNetd.setProcSysNet(family, INetd.NEIGH, mInterfaceParams.name, "mcast_resolicit",
+                        Integer.toString(numResolicits));
+            }
         }
 
         mNumSolicits = numSolicits;
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index 81d0c08..e06cdca 100755
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -255,6 +255,13 @@
      */
     public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra";
 
+    /**
+     * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
+     * set it to 3 by default.
+     */
+    public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
+            "ip_reachability_mcast_resolicit_version";
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
index 630b05d..2b00fb1 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -27,6 +27,8 @@
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
 import static android.net.dhcp.DhcpPacket.MIN_V6ONLY_WAIT_MS;
 import static android.net.dhcp.DhcpResultsParcelableUtil.fromStableParcelable;
+import static android.net.ip.IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM;
+import static android.net.ip.IpReachabilityMonitor.NUD_MCAST_RESOLICIT_NUM;
 import static android.net.ipmemorystore.Status.SUCCESS;
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IFA_F_TEMPORARY;
@@ -49,6 +51,7 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
@@ -474,6 +477,11 @@
                         NeighborEventConsumer cb) {
                     return new IpNeighborMonitor(h, log, cb);
                 }
+
+                public boolean isFeatureEnabled(final Context context, final String name,
+                        boolean defaultEnabled) {
+                    return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled);
+                }
             };
         }
 
@@ -3094,7 +3102,50 @@
         assertNeighborSolicitation(ns, target);
     }
 
+    private void assertMulticastNeighborSolicitation(final NeighborSolicitation ns,
+            final Inet6Address target) {
+        final MacAddress etherMulticast =
+                NetworkStackUtils.ipv6MulticastToEthernetMulticast(ns.ipv6Hdr.dstIp);
+        assertEquals(etherMulticast, ns.ethHdr.dstMac);
+        assertTrue(ns.ipv6Hdr.dstIp.isMulticastAddress());
+        assertNeighborSolicitation(ns, target);
+    }
+
+    private NeighborSolicitation waitForUnicastNeighborSolicitation(final MacAddress dstMac,
+            final Inet6Address dstIp, final Inet6Address targetIp) throws Exception {
+        NeighborSolicitation ns;
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) break;
+        }
+        assertNotNull("No unicast Neighbor solicitation received on interface within timeout", ns);
+        assertUnicastNeighborSolicitation(ns, dstMac, dstIp, targetIp);
+        return ns;
+    }
+
+    private List<NeighborSolicitation> waitForMultipleNeighborSolicitations() throws Exception {
+        NeighborSolicitation ns;
+        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
+        while ((ns = getNextNeighborSolicitation()) != null) {
+            // Filter out the NSes used for duplicate address detetction, the target address
+            // is the global IPv6 address inside these NSes.
+            if (ns.nsHdr.target.isLinkLocalAddress()) {
+                nsList.add(ns);
+            }
+        }
+        assertFalse(nsList.isEmpty());
+        return nsList;
+    }
+
+    // Override this function with disabled experiment flag by default, in order not to
+    // affect those tests which are just related to basic IpReachabilityMonitor infra.
     private void prepareIpReachabilityMonitorTest() throws Exception {
+        prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
+    }
+
+    private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
+            throws Exception {
         final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -3103,6 +3154,8 @@
                 .withDisplayName(TEST_DEFAULT_SSID)
                 .withoutIPv4()
                 .build();
+        setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
+                isMulticastResolicitEnabled);
         startIpClientProvisioning(config);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(false);
         doIpv6OnlyProvisioning();
@@ -3115,16 +3168,8 @@
     public void testIpReachabilityMonitor_probeFailed() throws Exception {
         prepareIpReachabilityMonitorTest();
 
-        NeighborSolicitation packet;
-        final List<NeighborSolicitation> nsList = new ArrayList<NeighborSolicitation>();
-        while ((packet = getNextNeighborSolicitation()) != null) {
-            // Filter out the NSes used for duplicate address detetction, the target address
-            // is the global IPv6 address inside these NSes.
-            if (packet.nsHdr.target.isLinkLocalAddress()) {
-                nsList.add(packet);
-            }
-        }
-        assertEquals(IpReachabilityMonitor.MIN_NUD_SOLICIT_NUM, nsList.size());
+        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+        assertEquals(MIN_NUD_SOLICIT_NUM, nsList.size());
         for (NeighborSolicitation ns : nsList) {
             assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
                     ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -3136,13 +3181,7 @@
     public void testIpReachabilityMonitor_probeReachable() throws Exception {
         prepareIpReachabilityMonitorTest();
 
-        NeighborSolicitation ns;
-        while ((ns = getNextNeighborSolicitation()) != null) {
-            // Filter out the NSes used for duplicate address detetction, the target address
-            // is the global IPv6 address inside these NSes.
-            if (ns.nsHdr.target.isLinkLocalAddress()) break;
-        }
-        assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
                 ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
 
         // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
@@ -3153,4 +3192,61 @@
         mPacketReader.sendResponse(na);
         assertNeverNotifyNeighborLost();
     }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeFailed() throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
+        int expectedSize = MIN_NUD_SOLICIT_NUM + NUD_MCAST_RESOLICIT_NUM;
+        assertEquals(expectedSize, nsList.size()); // 5 unicast NSes + 3 multicast NSes
+        for (NeighborSolicitation ns : nsList.subList(0, MIN_NUD_SOLICIT_NUM)) {
+            assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
+                    ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        for (NeighborSolicitation ns : nsList.subList(MIN_NUD_SOLICIT_NUM, nsList.size())) {
+            assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+        }
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithSameLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement and check notifyLost callback won't be triggered.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
+        final ByteBuffer na = NeighborAdvertisement.build(ROUTER_MAC /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNeverNotifyNeighborLost();
+    }
+
+    @Test
+    public void testIpReachabilityMonitor_mcastResoclicitProbeReachableWithDiffLinkLayerAddress()
+            throws Exception {
+        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+
+        final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
+                ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
+
+        // Reply Neighbor Advertisement with a different link-layer address and check notifyLost
+        // callback will be triggered. Override flag must be set, which indicates that the
+        // advertisement should override an existing cache entry and update the cached link-layer
+        // address, otherwise, kernel won't transit to REACHABLE state with a different link-layer
+        // address.
+        int flag = NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER | NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+                | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+        final MacAddress newMac = MacAddress.fromString("00:1a:11:22:33:55");
+        final ByteBuffer na = NeighborAdvertisement.build(newMac /* srcMac */,
+                ns.ethHdr.srcMac /* dstMac */, ROUTER_LINK_LOCAL /* srcIp */,
+                ns.ipv6Hdr.srcIp /* dstIp */, flag, ROUTER_LINK_LOCAL /* target */);
+        mPacketReader.sendResponse(na);
+        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */);
+    }
 }
diff --git a/tests/unit/lint-baseline.xml b/tests/unit/lint-baseline.xml
index 0812c2d..0bfcaa9 100644
--- a/tests/unit/lint-baseline.xml
+++ b/tests/unit/lint-baseline.xml
@@ -78,81 +78,4 @@
             column="14"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        mCallback.onCapabilitiesChanged(net2097, NetworkCapabilities())"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="59"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        mCallback.onCapabilitiesChanged(net2098, NetworkCapabilities())"
-        errorLine2="                                                 ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="71"
-            column="50"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val meteredNc = NetworkCapabilities()"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="108"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="109"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val netCaps = NetworkCapabilities().addTransportType(CELLULAR)"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="130"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="152"
-            column="23"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="            &quot;CapabilitiesChanged&quot; -> cb.onCapabilitiesChanged(net, NetworkCapabilities())"
-        errorLine2="                                                                   ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="packages/modules/NetworkStack/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt"
-            line="279"
-            column="68"/>
-    </issue>
-
 </issues>
diff --git a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
index 596db7a..6f495e7 100644
--- a/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
+++ b/tests/unit/src/android/net/testutils/TestableNetworkCallbackTest.kt
@@ -1,5 +1,6 @@
 package android.net.testutils
 
+import android.annotation.SuppressLint
 import android.net.LinkAddress
 import android.net.LinkProperties
 import android.net.Network
@@ -42,6 +43,7 @@
 const val TEST_INTERFACE_NAME = "testInterfaceName"
 
 @RunWith(JUnit4::class)
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 class TestableNetworkCallbackTest {
     private lateinit var mCallback: TestableNetworkCallback
 
@@ -298,6 +300,7 @@
     return CallbackEntry::class.sealedSubclasses.first { it.simpleName == name }
 }
 
+@SuppressLint("NewApi") // Uses hidden APIs, which the linter would identify as missing APIs.
 private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
     // Interpret "Available(xx)" as "call to onAvailable with netId xx", and likewise for
     // all callback types. This is implemented above by enumerating the subclasses of