Merge "Fix setAdministratorUids tests on Q"
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 7ff954b..651494d 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -1074,6 +1074,21 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 unreachable default route.
+     *
+     * @return {@code true} if there is an IPv4 unreachable default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIpv4UnreachableDefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+            if (r.isIPv4UnreachableDefault()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * For backward compatibility.
      * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
      * just yet.
@@ -1102,6 +1117,21 @@
     }
 
     /**
+     * Returns true if this link has an IPv6 unreachable default route.
+     *
+     * @return {@code true} if there is an IPv6 unreachable default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIpv6UnreachableDefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+            if (r.isIPv6UnreachableDefault()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * For backward compatibility.
      * This was annotated with @UnsupportedAppUsage in P, so we can't remove the method completely
      * just yet.
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 8119df9..fe90a84 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -36,6 +36,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -47,7 +48,7 @@
  * An agent manages the life cycle of a network. A network starts its
  * life cycle when {@link register} is called on NetworkAgent. The network
  * is then connecting. When full L3 connectivity has been established,
- * the agent shoud call {@link setConnected} to inform the system that
+ * the agent shoud call {@link markConnected} to inform the system that
  * this network is ready to use. When the network disconnects its life
  * ends and the agent should call {@link unregister}, at which point the
  * system will clean up and free resources.
@@ -503,7 +504,8 @@
                     break;
                 }
                 case CMD_START_SOCKET_KEEPALIVE: {
-                    onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+                    onStartSocketKeepalive(msg.arg1 /* slot */,
+                            Duration.ofSeconds(msg.arg2) /* interval */,
                             (KeepalivePacketData) msg.obj /* packet */);
                     break;
                 }
@@ -617,10 +619,10 @@
      * Inform ConnectivityService that this agent has now connected.
      * Call {@link #unregister} to disconnect.
      */
-    public void setConnected() {
+    public void markConnected() {
         if (mIsLegacy) {
             throw new UnsupportedOperationException(
-                    "Legacy agents can't call setConnected.");
+                    "Legacy agents can't call markConnected.");
         }
         mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
         queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
@@ -798,8 +800,8 @@
      * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
      * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
      *
-     * This may be called multiple times as network status changes, or if there are multiple
-     * subsequent attempts to validate connectivity that fail.
+     * This is guaranteed to be called again when the network status changes, but the system
+     * may also call this multiple times even if the status does not change.
      *
      * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
      * @param redirectUri If Internet connectivity is being redirected (e.g., on a captive portal),
@@ -832,18 +834,25 @@
      * Requests that the network hardware send the specified packet at the specified interval.
      *
      * @param slot the hardware slot on which to start the keepalive.
-     * @param intervalSeconds the interval between packets
+     * @param interval the interval between packets, between 10 and 3600. Note that this API
+     *                 does not support sub-second precision and will round off the request.
      * @param packet the packet to send.
      */
     // seconds is from SocketKeepalive.MIN_INTERVAL_SEC to MAX_INTERVAL_SEC, but these should
     // not be exposed as constants because they may change in the future (API guideline 4.8)
     // and should have getters if exposed at all. Getters can't be used in the annotation,
     // so the values unfortunately need to be copied.
-    public void onStartSocketKeepalive(int slot,
-            @IntRange(from = 10, to = 3600) int intervalSeconds,
+    public void onStartSocketKeepalive(int slot, @NonNull Duration interval,
             @NonNull KeepalivePacketData packet) {
-        Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
-                packet);
+        final long intervalSeconds = interval.getSeconds();
+        if (intervalSeconds < SocketKeepalive.MIN_INTERVAL_SEC
+                || intervalSeconds > SocketKeepalive.MAX_INTERVAL_SEC) {
+            throw new IllegalArgumentException("Interval needs to be comprised between "
+                    + SocketKeepalive.MIN_INTERVAL_SEC + " and " + SocketKeepalive.MAX_INTERVAL_SEC
+                    + " but was " + intervalSeconds);
+        }
+        final Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot,
+                (int) intervalSeconds, packet);
         startSocketKeepalive(msg);
         msg.recycle();
     }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 995cb72..240386a 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -1177,7 +1177,7 @@
     }
 
     private boolean satisfiedBySpecifier(NetworkCapabilities nc) {
-        return mNetworkSpecifier == null || mNetworkSpecifier.satisfiedBy(nc.mNetworkSpecifier)
+        return mNetworkSpecifier == null || mNetworkSpecifier.canBeSatisfiedBy(nc.mNetworkSpecifier)
                 || nc.mNetworkSpecifier instanceof MatchAllNetworkSpecifier;
     }
 
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 75fe6b1..b1e9c35 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -474,7 +474,7 @@
      * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
      *           satisfy any request.
      */
-    public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+    public boolean canBeSatisfiedBy(@Nullable NetworkCapabilities nc) {
         return networkCapabilities.satisfiedByNetworkCapabilities(nc);
     }
 
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index dbdaa4c..e550f85 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -426,6 +426,16 @@
     }
 
     /**
+     * Indicates if this route is an unreachable default route.
+     *
+     * @return {@code true} if it's an unreachable route with prefix length of 0.
+     * @hide
+     */
+    private boolean isUnreachableDefaultRoute() {
+        return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0;
+    }
+
+    /**
      * Indicates if this route is an IPv4 default route.
      * @hide
      */
@@ -434,6 +444,14 @@
     }
 
     /**
+     * Indicates if this route is an IPv4 unreachable default route.
+     * @hide
+     */
+    public boolean isIPv4UnreachableDefault() {
+        return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address;
+    }
+
+    /**
      * Indicates if this route is an IPv6 default route.
      * @hide
      */
@@ -442,6 +460,14 @@
     }
 
     /**
+     * Indicates if this route is an IPv6 unreachable default route.
+     * @hide
+     */
+    public boolean isIPv6UnreachableDefault() {
+        return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address;
+    }
+
+    /**
      * Indicates if this route is a host route (ie, matches only a single host address).
      *
      * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6,
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index 8ff8f4c..46aef10 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -115,7 +115,8 @@
             SUCCESS,
             ERROR_INVALID_LENGTH,
             ERROR_UNSUPPORTED,
-            ERROR_INSUFFICIENT_RESOURCES
+            ERROR_INSUFFICIENT_RESOURCES,
+            ERROR_HARDWARE_UNSUPPORTED
     })
     public @interface KeepaliveEvent {}
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0b024e7..ea91395 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3144,7 +3144,7 @@
             }
         }
 
-        nai.clatd.setNat64Prefix(prefix);
+        nai.clatd.setNat64PrefixFromDns(prefix);
         handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
     }
 
@@ -5856,9 +5856,9 @@
             @NonNull LinkProperties oldLp) {
         int netId = networkAgent.network.netId;
 
-        // The NetworkAgentInfo does not know whether clatd is running on its network or not, or
-        // whether there is a NAT64 prefix. Before we do anything else, make sure its LinkProperties
-        // are accurate.
+        // The NetworkAgent does not know whether clatd is running on its network or not, or whether
+        // a NAT64 prefix was discovered by the DNS resolver. Before we do anything else, make sure
+        // the LinkProperties for the network are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
         updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities,
@@ -6304,7 +6304,8 @@
                 && !nai.networkAgentConfig.allowBypass
                 && nc.getOwnerUid() != Process.SYSTEM_UID
                 && lp.getInterfaceName() != null
-                && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
+                && (lp.hasIPv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
+                && (lp.hasIPv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
     }
 
     private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 82465f8..3b3c520 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -81,7 +81,7 @@
         RUNNING,      // start() called, and the stacked iface is known to be up.
     }
 
-    private IpPrefix mNat64Prefix;
+    private IpPrefix mNat64PrefixFromDns;
     private String mBaseIface;
     private String mIface;
     private Inet6Address mIPv6Address;
@@ -100,7 +100,7 @@
      * currently connected and where the NetworkAgent has not disabled 464xlat. It is the signal to
      * enable NAT64 prefix discovery.
      *
-     * @param network the NetworkAgentInfo corresponding to the network.
+     * @param nai the NetworkAgentInfo corresponding to the network.
      * @return true if the network requires clat, false otherwise.
      */
     @VisibleForTesting
@@ -180,7 +180,7 @@
 
         String addrStr = null;
         try {
-            addrStr = mNetd.clatdStart(baseIface, mNat64Prefix.toString());
+            addrStr = mNetd.clatdStart(baseIface, getNat64Prefix().toString());
         } catch (RemoteException | ServiceSpecificException e) {
             Slog.e(TAG, "Error starting clatd on " + baseIface + ": " + e);
         }
@@ -318,8 +318,12 @@
         }
     }
 
-    public void setNat64Prefix(IpPrefix nat64Prefix) {
-        mNat64Prefix = nat64Prefix;
+    private IpPrefix getNat64Prefix() {
+        return mNat64PrefixFromDns;
+    }
+
+    public void setNat64PrefixFromDns(IpPrefix prefix) {
+        mNat64PrefixFromDns = prefix;
     }
 
     /**
@@ -328,7 +332,7 @@
      * has no idea that 464xlat is running on top of it.
      */
     public void fixupLinkProperties(@NonNull LinkProperties oldLp, @NonNull LinkProperties lp) {
-        lp.setNat64Prefix(mNat64Prefix);
+        lp.setNat64Prefix(getNat64Prefix());
 
         if (!isRunning()) {
             return;
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 8de27e8..0fc9be3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.net.RouteInfo.RTN_UNREACHABLE;
+
 import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
 import static com.android.testutils.ParcelUtilsKt.assertParcelingIsLossless;
 import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
@@ -46,6 +48,7 @@
 import org.junit.runner.RunWith;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1257,4 +1260,26 @@
         final LinkProperties Ipv6 = makeIpv6LinkProperties();
         assertTrue(Ipv6.hasIpv6DnsServer());
     }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testHasIpv4UnreachableDefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
+        assertTrue(lp.hasIpv4UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testHasIpv6UnreachableDefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIpv6UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+        assertTrue(lp.hasIpv6UnreachableDefaultRoute());
+        assertFalse(lp.hasIpv4UnreachableDefaultRoute());
+    }
 }
diff --git a/tests/net/common/java/android/net/RouteInfoTest.java b/tests/net/common/java/android/net/RouteInfoTest.java
index 1658262..8204b49 100644
--- a/tests/net/common/java/android/net/RouteInfoTest.java
+++ b/tests/net/common/java/android/net/RouteInfoTest.java
@@ -31,6 +31,7 @@
 
 import android.os.Build;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -62,6 +63,11 @@
         return new IpPrefix(prefix);
     }
 
+    private static boolean isAtLeastR() {
+        // BuildCompat.isAtLeastR is documented to return false on release SDKs (including R)
+        return Build.VERSION.SDK_INT > Build.VERSION_CODES.Q || BuildCompat.isAtLeastR();
+    }
+
     @Test
     public void testConstructor() {
         RouteInfo r;
@@ -195,78 +201,130 @@
         assertTrue(r.isDefaultRoute());
         assertTrue(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
         assertFalse(r.isHostRoute());
         assertTrue(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertTrue(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
         assertTrue(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertTrue(r.isIPv4UnreachableDefault());
+            assertFalse(r.isIPv6UnreachableDefault());
+        }
 
         r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
         assertFalse(r.isHostRoute());
         assertFalse(r.isDefaultRoute());
         assertFalse(r.isIPv4Default());
         assertFalse(r.isIPv6Default());
+        if (isAtLeastR()) {
+            assertFalse(r.isIPv4UnreachableDefault());
+            assertTrue(r.isIPv6UnreachableDefault());
+        }
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
new file mode 100644
index 0000000..9119d62
--- /dev/null
+++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats
+
+import android.net.NetworkStats
+import android.net.NetworkStats.DEFAULT_NETWORK_NO
+import android.net.NetworkStats.DEFAULT_NETWORK_YES
+import android.net.NetworkStats.Entry
+import android.net.NetworkStats.IFACE_VT
+import android.net.NetworkStats.METERED_NO
+import android.net.NetworkStats.METERED_YES
+import android.net.NetworkStats.ROAMING_NO
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.SET_DEFAULT
+import android.net.NetworkStats.SET_FOREGROUND
+import android.net.NetworkStats.TAG_NONE
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.assertFieldCountEquals
+import com.android.testutils.assertNetworkStatsEquals
+import com.android.testutils.assertParcelingIsLossless
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+
+@RunWith(JUnit4::class)
+@SmallTest
+class NetworkStatsApiTest {
+    @Rule
+    @JvmField
+    val ignoreRule = DevSdkIgnoreRule()
+
+    private val testStatsEmpty = NetworkStats(0L, 0)
+
+    // stats1 and stats2 will have some entries with common keys, which are expected to
+    // be merged if performing add on these 2 stats.
+    private val testStats1 = NetworkStats(0L, 0)
+            // Entries which only appear in set1.
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
+            // Entries which are common for set1 and set2.
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
+            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
+            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
+
+    private val testStats2 = NetworkStats(0L, 0)
+            // Entries which are common for set1 and set2.
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
+            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
+            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
+            // Entry which only appears in set2.
+            .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+
+    // This is a result of adding stats1 and stats2, while the merging of common key items is
+    // subject to test later, this should not be initialized with for a loop to add stats1
+    // and stats2 above.
+    private val testStats3 = NetworkStats(0L, 9)
+            // Entries which are unique either in stats1 or stats2.
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+            .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+            // Entries which are common for stats1 and stats2 are being merged.
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
+            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
+            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
+            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
+
+    companion object {
+        private const val TEST_IFACE = "test0"
+        private const val TEST_UID1 = 1001
+        private const val TEST_UID2 = 1002
+    }
+
+    @Before
+    fun setUp() {
+        assertEquals(8, testStats1.size())
+        assertEquals(5, testStats2.size())
+        assertEquals(9, testStats3.size())
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testAddEntry() {
+        val expectedEntriesInStats2 = arrayOf(
+                Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1),
+                Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45),
+                Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7),
+                Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0),
+                Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+
+        // While testStats* are already initialized with addEntry, verify content added
+        // matches expectation.
+        for (i in expectedEntriesInStats2.indices) {
+            val entry = testStats2.getValues(i, null)
+            assertEquals(expectedEntriesInStats2[i], entry)
+        }
+
+        // Verify entry updated with addEntry.
+        val stats = testStats2.addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9))
+        assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9),
+                stats.getValues(3, null))
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testAdd() {
+        var stats = NetworkStats(0L, 0)
+        assertNetworkStatsEquals(testStatsEmpty, stats)
+        stats = stats.add(testStats2)
+        assertNetworkStatsEquals(testStats2, stats)
+        stats = stats.add(testStats1)
+        // EMPTY + STATS2 + STATS1 = STATS3
+        assertNetworkStatsEquals(testStats3, stats)
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testParcelUnparcel() {
+        assertParcelingIsLossless(testStatsEmpty)
+        assertParcelingIsLossless(testStats1)
+        assertParcelingIsLossless(testStats2)
+        assertFieldCountEquals(15, NetworkStats::class.java)
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testDescribeContents() {
+        assertEquals(0, testStatsEmpty.describeContents())
+        assertEquals(0, testStats1.describeContents())
+        assertEquals(0, testStats2.describeContents())
+        assertEquals(0, testStats3.describeContents())
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testSubtract() {
+        // STATS3 - STATS2 = STATS1
+        assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2))
+        // STATS3 - STATS1 = STATS2
+        assertNetworkStatsEquals(testStats2, testStats3.subtract(testStats1))
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    fun testMethodsDontModifyReceiver() {
+        listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach {
+            val origStats = it.clone()
+            it.addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
+            it.add(testStats3)
+            it.subtract(testStats1)
+            assertNetworkStatsEquals(origStats, it)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index e71d599..98f705f 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -503,6 +503,53 @@
     }
 
     @Test
+    public void testRemoveEmptyEntries() throws Exception {
+        // Test empty stats.
+        final NetworkStats statsEmpty = new NetworkStats(TEST_START, 3);
+        assertEquals(0, statsEmpty.removeEmptyEntries().size());
+
+        // Test stats with non-zero entry.
+        final NetworkStats statsNonZero = new NetworkStats(TEST_START, 1)
+                .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+        assertEquals(1, statsNonZero.size());
+        final NetworkStats expectedNonZero = statsNonZero.removeEmptyEntries();
+        assertEquals(1, expectedNonZero.size());
+        assertValues(expectedNonZero, 0, TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 128L, 0L, 2L, 20L);
+
+        // Test stats with empty entry.
+        final NetworkStats statsZero = new NetworkStats(TEST_START, 1)
+                .insertEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, METERED_NO,
+                        ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+        assertEquals(1, statsZero.size());
+        final NetworkStats expectedZero = statsZero.removeEmptyEntries();
+        assertEquals(1, statsZero.size()); // Assert immutable.
+        assertEquals(0, expectedZero.size());
+
+        // Test stats with multiple entries.
+        final NetworkStats statsMultiple = new NetworkStats(TEST_START, 0)
+                .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+                .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+                .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L)
+                .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 0L)
+                .insertEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+                .insertEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 0L, 8L, 0L, 0L, 0L)
+                .insertEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 4L, 0L, 0L)
+                .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 2L, 0L)
+                .insertEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 0L, 0L, 0L, 0L, 1L);
+        assertEquals(9, statsMultiple.size());
+        final NetworkStats expectedMultiple = statsMultiple.removeEmptyEntries();
+        assertEquals(9, statsMultiple.size()); // Assert immutable.
+        assertEquals(7, expectedMultiple.size());
+        assertValues(expectedMultiple.getTotalIncludingTags(null), 14L, 104L, 4L, 4L, 21L);
+
+        // Test stats with multiple empty entries.
+        assertEquals(statsMultiple.size(), statsMultiple.subtract(statsMultiple).size());
+        assertEquals(0, statsMultiple.subtract(statsMultiple).removeEmptyEntries().size());
+    }
+
+    @Test
     public void testClone() throws Exception {
         final NetworkStats original = new NetworkStats(TEST_START, 5)
                 .insertEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 7b9d2bd..5cf7d72 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2913,7 +2913,7 @@
         class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements
                 Parcelable {
             @Override
-            public boolean satisfiedBy(NetworkSpecifier other) {
+            public boolean canBeSatisfiedBy(NetworkSpecifier other) {
                 return true;
             }
 
@@ -2941,7 +2941,7 @@
             }
 
             @Override
-            public boolean satisfiedBy(NetworkSpecifier other) {
+            public boolean canBeSatisfiedBy(NetworkSpecifier other) {
                 if (other instanceof LocalStringNetworkSpecifier) {
                     return TextUtils.equals(mString,
                             ((LocalStringNetworkSpecifier) other).mString);
@@ -3062,7 +3062,10 @@
         });
 
         class NonParcelableSpecifier extends NetworkSpecifier {
-            public boolean satisfiedBy(NetworkSpecifier other) { return false; }
+            @Override
+            public boolean canBeSatisfiedBy(NetworkSpecifier other) {
+                return false;
+            }
         };
         class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable {
             @Override public int describeContents() { return 0; }
@@ -6330,6 +6333,7 @@
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
         final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
@@ -6355,6 +6359,7 @@
     public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
@@ -6386,6 +6391,7 @@
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
         final TestNetworkAgentWrapper vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
@@ -6422,6 +6428,7 @@
         reset(mMockNetd);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         vpnNetworkAgent.sendLinkProperties(lp);
         waitForIdle();
@@ -6434,6 +6441,7 @@
     public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
+        lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
         lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
         // The uid range needs to cover the test app so the network is visible to it.
         final UidRange vpnRange = UidRange.createForUser(VPN_USER);
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index 9b24887..d0ebb52 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -181,7 +181,7 @@
         Nat464Xlat nat = makeNat464Xlat();
         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
 
-        nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+        nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         // Start clat.
         nat.start();
@@ -222,7 +222,7 @@
         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
         InOrder inOrder = inOrder(mNetd, mConnectivity);
 
-        nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+        nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
 
@@ -309,7 +309,7 @@
         Nat464Xlat nat = makeNat464Xlat();
         ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
 
-        nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+        nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
 
@@ -348,7 +348,7 @@
     public void testStopBeforeClatdStarts() throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
 
-        nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+        nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();
 
@@ -380,7 +380,7 @@
     public void testStopAndClatdNeverStarts() throws Exception {
         Nat464Xlat nat = makeNat464Xlat();
 
-        nat.setNat64Prefix(new IpPrefix(NAT64_PREFIX));
+        nat.setNat64PrefixFromDns(new IpPrefix(NAT64_PREFIX));
 
         nat.start();