Merge "Only apply VPN isolation if it's fully routed"
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index 6f0a4f9..9086d49 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -659,7 +659,8 @@
     public abstract static class ConnectivityDiagnosticsCallback {
         /**
          * Called when the platform completes a data connectivity check. This will also be invoked
-         * upon registration with the latest report.
+         * immediately upon registration for each network matching the request with the latest
+         * report, if a report has already been generated for that network.
          *
          * <p>The Network specified in the ConnectivityReport may not be active any more when this
          * method is invoked.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0f48ac2..6892a94 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -705,6 +705,36 @@
     @Deprecated
     public static final int TYPE_TEST = 18; // TODO: Remove this once NetworkTypes are unused.
 
+    /**
+     * @deprecated Use {@link NetworkCapabilities} instead.
+     * @hide
+     */
+    @Deprecated
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TYPE_" }, value = {
+                TYPE_NONE,
+                TYPE_MOBILE,
+                TYPE_WIFI,
+                TYPE_MOBILE_MMS,
+                TYPE_MOBILE_SUPL,
+                TYPE_MOBILE_DUN,
+                TYPE_MOBILE_HIPRI,
+                TYPE_WIMAX,
+                TYPE_BLUETOOTH,
+                TYPE_DUMMY,
+                TYPE_ETHERNET,
+                TYPE_MOBILE_FOTA,
+                TYPE_MOBILE_IMS,
+                TYPE_MOBILE_CBS,
+                TYPE_WIFI_P2P,
+                TYPE_MOBILE_IA,
+                TYPE_MOBILE_EMERGENCY,
+                TYPE_PROXY,
+                TYPE_VPN,
+                TYPE_TEST
+    })
+    public @interface LegacyNetworkType {}
+
     // Deprecated constants for return values of startUsingNetworkFeature. They used to live
     // in com.android.internal.telephony.PhoneConstants until they were made inaccessible.
     private static final int DEPRECATED_PHONE_CONSTANT_APN_ALREADY_ACTIVE = 0;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 5c754a1..8119df9 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -32,18 +34,52 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * A Utility class for handling for communicating between bearer-specific
+ * A utility class for handling for communicating between bearer-specific
  * code and ConnectivityService.
  *
+ * 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
+ * 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.
+ * Any reconnection becomes a new logical network, so after a network
+ * is disconnected the agent cannot be used any more. Network providers
+ * should create a new NetworkAgent instance to handle new connections.
+ *
  * A bearer may have more than one NetworkAgent if it can simultaneously
  * support separate networks (IMS / Internet / MMS Apns on cellular, or
  * perhaps connections with different SSID or P2P for Wi-Fi).
  *
+ * This class supports methods to start and stop sending keepalive packets.
+ * Keepalive packets are typically sent at periodic intervals over a network
+ * with NAT when there is no other traffic to avoid the network forcefully
+ * closing the connection. NetworkAgents that manage technologies that
+ * have hardware support for keepalive should implement the related
+ * methods to save battery life. NetworkAgent that cannot get support
+ * without waking up the CPU should not, as this would be prohibitive in
+ * terms of battery - these agents should simply not override the related
+ * methods, which results in the implementation returning
+ * {@link SocketKeepalive.ERROR_UNSUPPORTED} as appropriate.
+ *
+ * Keepalive packets need to be sent at relatively frequent intervals
+ * (a few seconds to a few minutes). As the contents of keepalive packets
+ * depend on the current network status, hardware needs to be configured
+ * to send them and has a limited amount of memory to do so. The HAL
+ * formalizes this as slots that an implementation can configure to send
+ * the correct packets. Devices typically have a small number of slots
+ * per radio technology, and the specific number of slots for each
+ * technology is specified in configuration files.
+ * {@see SocketKeepalive} for details.
+ *
  * @hide
  */
 @SystemApi
@@ -65,7 +101,7 @@
     private final String LOG_TAG;
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
-    private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+    private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
     private volatile long mLastBwRefreshTime = 0;
     private static final long BW_REFRESH_MIN_WIN_MS = 500;
     private boolean mBandwidthUpdateScheduled = false;
@@ -74,6 +110,8 @@
     // into the internal API of ConnectivityService.
     @NonNull
     private NetworkInfo mNetworkInfo;
+    @NonNull
+    private final Object mRegisterLock = new Object();
 
     /**
      * The ID of the {@link NetworkProvider} that created this object, or
@@ -158,6 +196,14 @@
      */
     public static final int VALIDATION_STATUS_NOT_VALID = 2;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "VALIDATION_STATUS_" }, value = {
+            VALIDATION_STATUS_VALID,
+            VALIDATION_STATUS_NOT_VALID
+    })
+    public @interface ValidationStatus {}
+
     // TODO: remove.
     /** @hide */
     public static final int VALID_NETWORK = 1;
@@ -202,7 +248,7 @@
      * Sent by ConnectivityService to the NetworkAgent to request that the specified packet be sent
      * periodically on the given interval.
      *
-     *   arg1 = the slot number of the keepalive to start
+     *   arg1 = the hardware slot number of the keepalive to start
      *   arg2 = interval in seconds
      *   obj = KeepalivePacketData object describing the data to be sent
      *
@@ -214,7 +260,7 @@
     /**
      * Requests that the specified keepalive packet be stopped.
      *
-     * arg1 = slot number of the keepalive to stop.
+     * arg1 = hardware slot number of the keepalive to stop.
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
      * @hide
@@ -229,7 +275,7 @@
      * This is also sent by KeepaliveTracker to the app's {@link SocketKeepalive},
      * so that the app's {@link SocketKeepalive.Callback} methods can be called.
      *
-     * arg1 = slot number of the keepalive
+     * arg1 = hardware slot number of the keepalive
      * arg2 = error code
      * @hide
      */
@@ -259,7 +305,7 @@
      * remote site will send ACK packets in response to the keepalive packets, the firmware also
      * needs to be configured to properly filter the ACKs to prevent the system from waking up.
      * This does not happen with UDP, so this message is TCP-specific.
-     * arg1 = slot number of the keepalive to filter for.
+     * arg1 = hardware slot number of the keepalive to filter for.
      * obj = the keepalive packet to send repeatedly.
      * @hide
      */
@@ -268,7 +314,7 @@
     /**
      * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
      * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
-     * arg1 = slot number of the keepalive packet filter to remove.
+     * arg1 = hardware slot number of the keepalive packet filter to remove.
      * @hide
      */
     public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
@@ -441,7 +487,15 @@
                                 + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
                                 + redirectUrl);
                     }
-                    onValidationStatus(msg.arg1 /* status */, redirectUrl);
+                    Uri uri = null;
+                    try {
+                        if (null != redirectUrl) {
+                            uri = Uri.parse(redirectUrl);
+                        }
+                    } catch (Exception e) {
+                        Log.wtf(LOG_TAG, "Surprising URI : " + redirectUrl, e);
+                    }
+                    onValidationStatus(msg.arg1 /* status */, uri);
                     break;
                 }
                 case CMD_SAVE_ACCEPT_UNVALIDATED: {
@@ -489,19 +543,29 @@
 
     /**
      * Register this network agent with ConnectivityService.
+     *
+     * This method can only be called once per network agent.
+     *
      * @return the Network associated with this network agent (which can also be obtained later
      *         by calling getNetwork() on this agent).
+     * @throws IllegalStateException thrown by the system server if this network agent is
+     *         already registered.
      */
     @NonNull
     public Network register() {
         if (VDBG) log("Registering NetworkAgent");
         final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context
                 .getSystemService(Context.CONNECTIVITY_SERVICE);
-        mNetwork = cm.registerNetworkAgent(new Messenger(mHandler),
-                new NetworkInfo(mInitialConfiguration.info),
-                mInitialConfiguration.properties, mInitialConfiguration.capabilities,
-                mInitialConfiguration.score, mInitialConfiguration.config, providerId);
-        mInitialConfiguration = null; // All this memory can now be GC'd
+        synchronized (mRegisterLock) {
+            if (mNetwork != null) {
+                throw new IllegalStateException("Agent already registered");
+            }
+            mNetwork = cm.registerNetworkAgent(new Messenger(mHandler),
+                    new NetworkInfo(mInitialConfiguration.info),
+                    mInitialConfiguration.properties, mInitialConfiguration.capabilities,
+                    mInitialConfiguration.score, mInitialConfiguration.config, providerId);
+            mInitialConfiguration = null; // All this memory can now be GC'd
+        }
         return mNetwork;
     }
 
@@ -544,13 +608,14 @@
      * Must be called by the agent when the network's {@link LinkProperties} change.
      * @param linkProperties the new LinkProperties.
      */
-    public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
+    public final void sendLinkProperties(@NonNull LinkProperties linkProperties) {
         Objects.requireNonNull(linkProperties);
         queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
     }
 
     /**
      * Inform ConnectivityService that this agent has now connected.
+     * Call {@link #unregister} to disconnect.
      */
     public void setConnected() {
         if (mIsLegacy) {
@@ -569,8 +634,7 @@
      */
     public void unregister() {
         if (mIsLegacy) {
-            throw new UnsupportedOperationException(
-                    "Legacy agents can't call unregister.");
+            throw new UnsupportedOperationException("Legacy agents can't call unregister.");
         }
         mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
         queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, mNetworkInfo);
@@ -626,7 +690,7 @@
      * @hide TODO: expose something better.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public void sendNetworkInfo(NetworkInfo networkInfo) {
+    public final void sendNetworkInfo(NetworkInfo networkInfo) {
         if (!mIsLegacy) {
             throw new UnsupportedOperationException("Only legacy agents can call sendNetworkInfo.");
         }
@@ -637,7 +701,7 @@
      * Must be called by the agent when the network's {@link NetworkCapabilities} change.
      * @param networkCapabilities the new NetworkCapabilities.
      */
-    public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+    public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
         Objects.requireNonNull(networkCapabilities);
         mBandwidthUpdatePending.set(false);
         mLastBwRefreshTime = System.currentTimeMillis();
@@ -647,9 +711,10 @@
 
     /**
      * Must be called by the agent to update the score of this network.
-     * @param score the new score.
+     *
+     * @param score the new score, between 0 and 99.
      */
-    public void sendNetworkScore(int score) {
+    public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
         if (score < 0) {
             throw new IllegalArgumentException("Score must be >= 0");
         }
@@ -737,11 +802,11 @@
      * subsequent attempts to validate connectivity that fail.
      *
      * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
-     * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+     * @param redirectUri If Internet connectivity is being redirected (e.g., on a captive portal),
      *        this is the destination the probes are being redirected to, otherwise {@code null}.
      */
-    public void onValidationStatus(int status, @Nullable String redirectUrl) {
-        networkStatus(status, redirectUrl);
+    public void onValidationStatus(@ValidationStatus int status, @Nullable Uri redirectUri) {
+        networkStatus(status, redirectUri.toString());
     }
     /** @hide TODO delete once subclasses have moved to onValidationStatus */
     protected void networkStatus(int status, String redirectUrl) {
@@ -770,7 +835,12 @@
      * @param intervalSeconds the interval between packets
      * @param packet the packet to send.
      */
-    public void onStartSocketKeepalive(int slot, int intervalSeconds,
+    // 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,
             @NonNull KeepalivePacketData packet) {
         Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
                 packet);
@@ -801,9 +871,11 @@
      * Must be called by the agent when a socket keepalive event occurs.
      *
      * @param slot the hardware slot on which the event occurred.
-     * @param event the event that occurred.
+     * @param event the event that occurred, as one of the SocketKeepalive.ERROR_*
+     *              or SocketKeepalive.SUCCESS constants.
      */
-    public void sendSocketKeepaliveEvent(int slot, int event) {
+    public final void sendSocketKeepaliveEvent(int slot,
+            @SocketKeepalive.KeepaliveEvent int event) {
         queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
     }
     /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
@@ -845,9 +917,18 @@
     }
 
     /**
-     * Called by ConnectivityService to inform this network transport of signal strength thresholds
+     * Called by ConnectivityService to inform this network agent of signal strength thresholds
      * that when crossed should trigger a system wakeup and a NetworkCapabilities update.
      *
+     * When the system updates the list of thresholds that should wake up the CPU for a
+     * given agent it will call this method on the agent. The agent that implement this
+     * should implement it in hardware so as to ensure the CPU will be woken up on breach.
+     * Agents are expected to react to a breach by sending an updated NetworkCapabilities
+     * object with the appropriate signal strength to sendNetworkCapabilities.
+     *
+     * The specific units are bearer-dependent. See details on the units and requests in
+     * {@link NetworkCapabilities.Builder#setSignalStrength}.
+     *
      * @param thresholds the array of thresholds that should trigger wakeups.
      */
     public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
index ca9328a..fee868a 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -155,6 +155,7 @@
     /**
      * @return the legacy type
      */
+    @ConnectivityManager.LegacyNetworkType
     public int getLegacyType() {
         return legacyType;
     }
@@ -206,7 +207,7 @@
     /**
      * Builder class to facilitate constructing {@link NetworkAgentConfig} objects.
      */
-    public static class Builder {
+    public static final class Builder {
         private final NetworkAgentConfig mConfig = new NetworkAgentConfig();
 
         /**
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ad1e501..995cb72 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -118,7 +120,7 @@
         mTransportInfo = nc.mTransportInfo;
         mSignalStrength = nc.mSignalStrength;
         setUids(nc.mUids); // Will make the defensive copy
-        setAdministratorUids(nc.mAdministratorUids);
+        setAdministratorUids(nc.getAdministratorUids());
         mOwnerUid = nc.mOwnerUid;
         mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
         mSSID = nc.mSSID;
@@ -919,18 +921,22 @@
      * <p>For NetworkCapability instances being sent from the System Server, this value MUST be
      * empty unless the destination is 1) the System Server, or 2) Telephony. In either case, the
      * receiving entity must have the ACCESS_FINE_LOCATION permission and target R+.
+     *
+     * <p>When received from an app in a NetworkRequest this is always cleared out by the system
+     * server. This field is never used for matching NetworkRequests to NetworkAgents.
      */
-    private int[] mAdministratorUids = new int[0];
+    @NonNull private int[] mAdministratorUids = new int[0];
 
     /**
      * Sets the int[] of UIDs that are administrators of this network.
      *
      * <p>UIDs included in administratorUids gain administrator privileges over this Network.
      * Examples of UIDs that should be included in administratorUids are:
+     *
      * <ul>
-     *     <li>Carrier apps with privileges for the relevant subscription
-     *     <li>Active VPN apps
-     *     <li>Other application groups with a particular Network-related role
+     *   <li>Carrier apps with privileges for the relevant subscription
+     *   <li>Active VPN apps
+     *   <li>Other application groups with a particular Network-related role
      * </ul>
      *
      * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator.
@@ -938,19 +944,34 @@
      * <p>An app is granted owner privileges over Networks that it supplies. The owner UID MUST
      * always be included in administratorUids.
      *
+     * <p>The administrator UIDs are set by network agents.
+     *
      * @param administratorUids the UIDs to be set as administrators of this Network.
+     * @throws IllegalArgumentException if duplicate UIDs are contained in administratorUids
+     * @see #mAdministratorUids
      * @hide
      */
     @NonNull
     public NetworkCapabilities setAdministratorUids(@NonNull final int[] administratorUids) {
         mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length);
+        Arrays.sort(mAdministratorUids);
+        for (int i = 0; i < mAdministratorUids.length - 1; i++) {
+            if (mAdministratorUids[i] >= mAdministratorUids[i + 1]) {
+                throw new IllegalArgumentException("All administrator UIDs must be unique");
+            }
+        }
         return this;
     }
 
     /**
      * Retrieves the UIDs that are administrators of this Network.
      *
+     * <p>This is only populated in NetworkCapabilities objects that come from network agents for
+     * networks that are managed by specific apps on the system, such as carrier privileged apps or
+     * wifi suggestion apps. This will include the network owner.
+     *
      * @return the int[] of UIDs that are administrators of this Network
+     * @see #mAdministratorUids
      * @hide
      */
     @NonNull
@@ -961,6 +982,40 @@
     }
 
     /**
+     * Tests if the set of administrator UIDs of this network is the same as that of the passed one.
+     *
+     * <p>The administrator UIDs must be in sorted order.
+     *
+     * <p>nc is assumed non-null. Else, NPE.
+     *
+     * @hide
+     */
+    @VisibleForTesting(visibility = PRIVATE)
+    public boolean equalsAdministratorUids(@NonNull final NetworkCapabilities nc) {
+        return Arrays.equals(mAdministratorUids, nc.mAdministratorUids);
+    }
+
+    /**
+     * Combine the administrator UIDs of the capabilities.
+     *
+     * <p>This is only legal if either of the administrators lists are empty, or if they are equal.
+     * Combining administrator UIDs is only possible for combining non-overlapping sets of UIDs.
+     *
+     * <p>If both administrator lists are non-empty but not equal, they conflict with each other. In
+     * this case, it would not make sense to add them together.
+     */
+    private void combineAdministratorUids(@NonNull final NetworkCapabilities nc) {
+        if (nc.mAdministratorUids.length == 0) return;
+        if (mAdministratorUids.length == 0) {
+            mAdministratorUids = Arrays.copyOf(nc.mAdministratorUids, nc.mAdministratorUids.length);
+            return;
+        }
+        if (!equalsAdministratorUids(nc)) {
+            throw new IllegalStateException("Can't combine two different administrator UID lists");
+        }
+    }
+
+    /**
      * Value indicating that link bandwidth is unspecified.
      * @hide
      */
@@ -1455,6 +1510,7 @@
         combineUids(nc);
         combineSSIDs(nc);
         combineRequestor(nc);
+        combineAdministratorUids(nc);
     }
 
     /**
@@ -1568,7 +1624,8 @@
                 && equalsUids(that)
                 && equalsSSID(that)
                 && equalsPrivateDnsBroken(that)
-                && equalsRequestor(that);
+                && equalsRequestor(that)
+                && equalsAdministratorUids(that);
     }
 
     @Override
@@ -1588,7 +1645,8 @@
                 + Objects.hashCode(mTransportInfo) * 41
                 + Objects.hashCode(mPrivateDnsBroken) * 43
                 + Objects.hashCode(mRequestorUid) * 47
-                + Objects.hashCode(mRequestorPackageName) * 53;
+                + Objects.hashCode(mRequestorPackageName) * 53
+                + Arrays.hashCode(mAdministratorUids) * 59;
     }
 
     @Override
@@ -1609,7 +1667,7 @@
         dest.writeArraySet(mUids);
         dest.writeString(mSSID);
         dest.writeBoolean(mPrivateDnsBroken);
-        dest.writeIntArray(mAdministratorUids);
+        dest.writeIntArray(getAdministratorUids());
         dest.writeInt(mOwnerUid);
         dest.writeInt(mRequestorUid);
         dest.writeString(mRequestorPackageName);
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
index 418d691..75086cf 100644
--- a/core/java/android/net/NetworkProvider.java
+++ b/core/java/android/net/NetworkProvider.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -33,8 +34,8 @@
  * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
  * with via networking APIs such as {@link ConnectivityManager}.
  *
- * Subclasses should implement {@link #onNetworkRequested} and {@link #onRequestWithdrawn} to
- * receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the
+ * Subclasses should implement {@link #onNetworkRequested} and {@link #onNetworkRequestWithdrawn}
+ * to receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the
  * best (highest-scoring) network for any request is generally not used by the system, and torn
  * down.
  *
@@ -77,7 +78,7 @@
      * Constructs a new NetworkProvider.
      *
      * @param looper the Looper on which to run {@link #onNetworkRequested} and
-     *               {@link #onRequestWithdrawn}.
+     *               {@link #onNetworkRequestWithdrawn}.
      * @param name the name of the listener, used only for debugging.
      *
      * @hide
@@ -94,7 +95,7 @@
                         onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2);
                         break;
                     case CMD_CANCEL_REQUEST:
-                        onRequestWithdrawn((NetworkRequest) m.obj);
+                        onNetworkRequestWithdrawn((NetworkRequest) m.obj);
                         break;
                     default:
                         Log.e(mName, "Unhandled message: " + m.what);
@@ -142,14 +143,15 @@
      *  @hide
      */
     @SystemApi
-    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {}
+    public void onNetworkRequested(@NonNull NetworkRequest request,
+            @IntRange(from = 0, to = 99) int score, int providerId) {}
 
     /**
      *  Called when a NetworkRequest is withdrawn.
      *  @hide
      */
     @SystemApi
-    public void onRequestWithdrawn(@NonNull NetworkRequest request) {}
+    public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {}
 
     /**
      * Asserts that no provider will ever be able to satisfy the specified request. The provider
@@ -157,7 +159,7 @@
      * satisfying this request, and that the request cannot be satisfied. The application filing the
      * request will receive an {@link NetworkCallback#onUnavailable()} callback.
      *
-     * @param request the request that cannot be fulfilled
+     * @param request the request that permanently cannot be fulfilled
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index b0bf64e..75fe6b1 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -473,9 +473,7 @@
      *
      * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
      *           satisfy any request.
-     * @hide
      */
-    @SystemApi
     public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
         return networkCapabilities.satisfiedByNetworkCapabilities(nc);
     }
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fc9a8f6..8ff8f4c 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -109,6 +109,16 @@
     })
     public @interface ErrorCode {}
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            SUCCESS,
+            ERROR_INVALID_LENGTH,
+            ERROR_UNSUPPORTED,
+            ERROR_INSUFFICIENT_RESOURCES
+    })
+    public @interface KeepaliveEvent {}
+
     /**
      * The minimum interval in seconds between keepalive packet transmissions.
      *
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index db0ea44..0c45285 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -653,8 +653,8 @@
     final MultipathPolicyTracker mMultipathPolicyTracker;
 
     @VisibleForTesting
-    final Map<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo>
-            mConnectivityDiagnosticsCallbacks = new HashMap<>();
+    final Map<IBinder, ConnectivityDiagnosticsCallbackInfo> mConnectivityDiagnosticsCallbacks =
+            new HashMap<>();
 
     /**
      * Implements support for the legacy "one network per network type" model.
@@ -7827,11 +7827,12 @@
         ensureRunningOnConnectivityServiceThread();
 
         final IConnectivityDiagnosticsCallback cb = cbInfo.mCb;
+        final IBinder iCb = cb.asBinder();
         final NetworkRequestInfo nri = cbInfo.mRequestInfo;
 
         // This means that the client registered the same callback multiple times. Do
         // not override the previous entry, and exit silently.
-        if (mConnectivityDiagnosticsCallbacks.containsKey(cb)) {
+        if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) {
             if (VDBG) log("Diagnostics callback is already registered");
 
             // Decrement the reference count for this NetworkRequestInfo. The reference count is
@@ -7841,40 +7842,75 @@
             return;
         }
 
-        mConnectivityDiagnosticsCallbacks.put(cb, cbInfo);
+        mConnectivityDiagnosticsCallbacks.put(iCb, cbInfo);
 
         try {
-            cb.asBinder().linkToDeath(cbInfo, 0);
+            iCb.linkToDeath(cbInfo, 0);
         } catch (RemoteException e) {
             cbInfo.binderDied();
+            return;
+        }
+
+        // Once registered, provide ConnectivityReports for matching Networks
+        final List<NetworkAgentInfo> matchingNetworks = new ArrayList<>();
+        synchronized (mNetworkForNetId) {
+            for (int i = 0; i < mNetworkForNetId.size(); i++) {
+                final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
+                if (nai.satisfies(nri.request)) {
+                    matchingNetworks.add(nai);
+                }
+            }
+        }
+        for (final NetworkAgentInfo nai : matchingNetworks) {
+            final ConnectivityReport report = nai.getConnectivityReport();
+            if (report == null) {
+                continue;
+            }
+            if (!checkConnectivityDiagnosticsPermissions(
+                    nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
+                continue;
+            }
+
+            try {
+                cb.onConnectivityReportAvailable(report);
+            } catch (RemoteException e) {
+                // Exception while sending the ConnectivityReport. Move on to the next network.
+            }
         }
     }
 
     private void handleUnregisterConnectivityDiagnosticsCallback(
             @NonNull IConnectivityDiagnosticsCallback cb, int uid) {
         ensureRunningOnConnectivityServiceThread();
+        final IBinder iCb = cb.asBinder();
 
-        if (!mConnectivityDiagnosticsCallbacks.containsKey(cb)) {
+        final ConnectivityDiagnosticsCallbackInfo cbInfo =
+                mConnectivityDiagnosticsCallbacks.remove(iCb);
+        if (cbInfo == null) {
             if (VDBG) log("Removing diagnostics callback that is not currently registered");
             return;
         }
 
-        final NetworkRequestInfo nri = mConnectivityDiagnosticsCallbacks.get(cb).mRequestInfo;
+        final NetworkRequestInfo nri = cbInfo.mRequestInfo;
 
         if (uid != nri.mUid) {
             if (VDBG) loge("Different uid than registrant attempting to unregister cb");
             return;
         }
 
-        cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0);
+        // Decrement the reference count for this NetworkRequestInfo. The reference count is
+        // incremented when the NetworkRequestInfo is created as part of
+        // enforceRequestCountLimit().
+        decrementNetworkRequestPerUidCount(nri);
+
+        iCb.unlinkToDeath(cbInfo, 0);
     }
 
     private void handleNetworkTestedWithExtras(
             @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) {
         final NetworkAgentInfo nai = reportEvent.mNai;
         final NetworkCapabilities networkCapabilities =
-                new NetworkCapabilities(nai.networkCapabilities);
-        clearNetworkCapabilitiesUids(networkCapabilities);
+                getNetworkCapabilitiesWithoutUids(nai.networkCapabilities);
         final ConnectivityReport report =
                 new ConnectivityReport(
                         reportEvent.mNai.network,
@@ -7882,6 +7918,7 @@
                         nai.linkProperties,
                         networkCapabilities,
                         extras);
+        nai.setConnectivityReport(report);
         final List<IConnectivityDiagnosticsCallback> results =
                 getMatchingPermissionedCallbacks(nai);
         for (final IConnectivityDiagnosticsCallback cb : results) {
@@ -7897,8 +7934,7 @@
             @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod,
             @NonNull PersistableBundle extras) {
         final NetworkCapabilities networkCapabilities =
-                new NetworkCapabilities(nai.networkCapabilities);
-        clearNetworkCapabilitiesUids(networkCapabilities);
+                getNetworkCapabilitiesWithoutUids(nai.networkCapabilities);
         final DataStallReport report =
                 new DataStallReport(
                         nai.network,
@@ -7931,23 +7967,25 @@
         }
     }
 
-    private void clearNetworkCapabilitiesUids(@NonNull NetworkCapabilities nc) {
-        nc.setUids(null);
-        nc.setAdministratorUids(new int[0]);
-        nc.setOwnerUid(Process.INVALID_UID);
+    private NetworkCapabilities getNetworkCapabilitiesWithoutUids(@NonNull NetworkCapabilities nc) {
+        final NetworkCapabilities sanitized = new NetworkCapabilities(nc);
+        sanitized.setUids(null);
+        sanitized.setAdministratorUids(new int[0]);
+        sanitized.setOwnerUid(Process.INVALID_UID);
+        return sanitized;
     }
 
     private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
             @NonNull NetworkAgentInfo nai) {
         final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
-        for (Entry<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> entry :
+        for (Entry<IBinder, ConnectivityDiagnosticsCallbackInfo> entry :
                 mConnectivityDiagnosticsCallbacks.entrySet()) {
             final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
             final NetworkRequestInfo nri = cbInfo.mRequestInfo;
             if (nai.satisfies(nri.request)) {
                 if (checkConnectivityDiagnosticsPermissions(
                         nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
-                    results.add(entry.getKey());
+                    results.add(entry.getValue().mCb);
                 }
             }
         }
@@ -7984,11 +8022,7 @@
 
         // Administrator UIDs also contains the Owner UID
         final int[] administratorUids = nai.networkCapabilities.getAdministratorUids();
-        for (final int uid : administratorUids) {
-            if (uid == callbackUid) return true;
-        }
-
-        return false;
+        return ArrayUtils.contains(administratorUids, callbackUid);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index f844844..2d1f553 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -243,6 +244,10 @@
     // How many of the satisfied requests are of type BACKGROUND_REQUEST.
     private int mNumBackgroundNetworkRequests = 0;
 
+    // The last ConnectivityReport made available for this network. This value is only null before a
+    // report is generated. Once non-null, it will never be null again.
+    @Nullable private ConnectivityReport mConnectivityReport;
+
     public final Messenger messenger;
     public final AsyncChannel asyncChannel;
 
@@ -621,6 +626,32 @@
         for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
     }
 
+    /**
+     * Sets the most recent ConnectivityReport for this network.
+     *
+     * <p>This should only be called from the ConnectivityService thread.
+     *
+     * @hide
+     */
+    public void setConnectivityReport(@NonNull ConnectivityReport connectivityReport) {
+        mConnectivityReport = connectivityReport;
+    }
+
+    /**
+     * Returns the most recent ConnectivityReport for this network, or null if none have been
+     * reported yet.
+     *
+     * <p>This should only be called from the ConnectivityService thread.
+     *
+     * @hide
+     */
+    @Nullable
+    public ConnectivityReport getConnectivityReport() {
+        return mConnectivityReport;
+    }
+
+    // TODO: Print shorter members first and only print the boolean variable which value is true
+    // to improve readability.
     public String toString() {
         return "NetworkAgentInfo{"
                 + "network{" + network + "}  handle{" + network.getNetworkHandle() + "}  ni{"
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 916c339..316a83a 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -58,6 +58,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
@@ -280,6 +281,7 @@
             .addCapability(NET_CAPABILITY_NOT_METERED);
         if (isAtLeastR()) {
             netCap.setOwnerUid(123);
+            netCap.setAdministratorUids(new int[] {5, 11});
         }
         assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
@@ -440,6 +442,23 @@
     }
 
     @Test
+    public void testSetAdministratorUids() {
+        NetworkCapabilities nc =
+                new NetworkCapabilities().setAdministratorUids(new int[] {2, 1, 3});
+
+        assertArrayEquals(new int[] {1, 2, 3}, nc.getAdministratorUids());
+    }
+
+    @Test
+    public void testSetAdministratorUidsWithDuplicates() {
+        try {
+            new NetworkCapabilities().setAdministratorUids(new int[] {1, 1});
+            fail("Expected IllegalArgumentException for duplicate uids");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testCombineCapabilities() {
         NetworkCapabilities nc1 = new NetworkCapabilities();
         NetworkCapabilities nc2 = new NetworkCapabilities();
@@ -491,6 +510,25 @@
         assertFalse(nc2.appliesToUid(12));
         assertTrue(nc1.appliesToUid(22));
         assertTrue(nc2.appliesToUid(22));
+
+        final int[] adminUids = {3, 6, 12};
+        nc1.setAdministratorUids(adminUids);
+        nc2.combineCapabilities(nc1);
+        assertTrue(nc2.equalsAdministratorUids(nc1));
+        assertArrayEquals(nc2.getAdministratorUids(), adminUids);
+
+        final int[] adminUidsOtherOrder = {3, 12, 6};
+        nc1.setAdministratorUids(adminUids);
+        assertTrue(nc2.equalsAdministratorUids(nc1));
+
+        final int[] adminUids2 = {11, 1, 12, 3, 6};
+        nc1.setAdministratorUids(adminUids2);
+        assertFalse(nc2.equalsAdministratorUids(nc1));
+        assertFalse(Arrays.equals(nc2.getAdministratorUids(), adminUids2));
+        try {
+            nc2.combineCapabilities(nc1);
+            fail("Shouldn't be able to combine different lists of admin UIDs");
+        } catch (IllegalStateException expected) { }
     }
 
     @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/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java
index 8a4b533..ceca6f0 100644
--- a/tests/net/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java
@@ -65,7 +65,7 @@
         assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
         assertFalse(p.isBypassable);
         assertFalse(p.isMetered);
-        assertEquals(1400, p.maxMtu);
+        assertEquals(1360, p.maxMtu);
         assertFalse(p.areAuthParamsInline);
     }
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 75c71bb..f2e39cf 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -316,6 +316,8 @@
     private static final String TEST_PACKAGE_NAME = "com.android.test.package";
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
+    private static final String INTERFACE_NAME = "interface";
+
     private MockContext mServiceContext;
     private HandlerThread mCsHandlerThread;
     private ConnectivityService mService;
@@ -6747,16 +6749,12 @@
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
-        assertTrue(
-                mService.mConnectivityDiagnosticsCallbacks.containsKey(
-                        mConnectivityDiagnosticsCallback));
+        assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
 
         mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback);
         verify(mIBinder, timeout(TIMEOUT_MS))
                 .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
-        assertFalse(
-                mService.mConnectivityDiagnosticsCallbacks.containsKey(
-                        mConnectivityDiagnosticsCallback));
+        assertFalse(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
         verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder();
     }
 
@@ -6774,9 +6772,7 @@
 
         verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
         verify(mConnectivityDiagnosticsCallback).asBinder();
-        assertTrue(
-                mService.mConnectivityDiagnosticsCallbacks.containsKey(
-                        mConnectivityDiagnosticsCallback));
+        assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
 
         // Register the same callback again
         mService.registerConnectivityDiagnosticsCallback(
@@ -6785,9 +6781,7 @@
         // Block until all other events are done processing.
         HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
 
-        assertTrue(
-                mService.mConnectivityDiagnosticsCallbacks.containsKey(
-                        mConnectivityDiagnosticsCallback));
+        assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder));
     }
 
     @Test
@@ -6914,6 +6908,38 @@
                         mContext.getOpPackageName()));
     }
 
+    @Test
+    public void testRegisterConnectivityDiagnosticsCallbackCallsOnConnectivityReport()
+            throws Exception {
+        // Set up the Network, which leads to a ConnectivityReport being cached for the network.
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(callback);
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(INTERFACE_NAME);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties);
+        mCellNetworkAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        callback.assertNoCallback();
+
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+        when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
+
+        mServiceContext.setPermission(
+                android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        verify(mConnectivityDiagnosticsCallback)
+                .onConnectivityReportAvailable(argThat(report -> {
+                    return INTERFACE_NAME.equals(report.getLinkProperties().getInterfaceName())
+                            && report.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR);
+                }));
+    }
+
     private void setUpConnectivityDiagnosticsCallback() throws Exception {
         final NetworkRequest request = new NetworkRequest.Builder().build();
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);