Merge "Add MdnsServiceCache"
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index ed86854..31990fb 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -222,29 +222,16 @@
 
     @Test
     public void testHttpEngine_EnableQuic() throws Exception {
+        String url = mTestServer.getSuccessUrl();
         mEngine = mEngineBuilder.setEnableQuic(true).addQuicHint(HOST, 443, 443).build();
-        // The hint doesn't guarantee that QUIC will win the race, just that it will race TCP.
-        // We send multiple requests to reduce the flakiness of the test.
-        boolean quicWasUsed = false;
-        for (int i = 0; i < 5; i++) {
-            mCallback = new TestUrlRequestCallback();
-            UrlRequest.Builder builder =
-                    mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
-            mRequest = builder.build();
-            mRequest.start();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
 
-            // This tests uses a non-hermetic server. Instead of asserting, assume the next
-            // callback. This way, if the request were to fail, the test would just be skipped
-            // instead of failing.
-            mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
-            UrlResponseInfo info = mCallback.mResponseInfo;
-            assumeOKStatusCode(info);
-            quicWasUsed = isQuic(info.getNegotiatedProtocol());
-            if (quicWasUsed) {
-                break;
-            }
-        }
-        assertTrue(quicWasUsed);
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
     }
 
     @Test
@@ -379,34 +366,22 @@
 
     @Test
     public void testHttpEngine_SetQuicOptions_RequestSucceedsWithQuic() throws Exception {
+        String url = mTestServer.getSuccessUrl();
         QuicOptions options = new QuicOptions.Builder().build();
         mEngine = mEngineBuilder
                 .setEnableQuic(true)
                 .addQuicHint(HOST, 443, 443)
                 .setQuicOptions(options)
                 .build();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
 
-        // The hint doesn't guarantee that QUIC will win the race, just that it will race TCP.
-        // We send multiple requests to reduce the flakiness of the test.
-        boolean quicWasUsed = false;
-        for (int i = 0; i < 5; i++) {
-            mCallback = new TestUrlRequestCallback();
-            UrlRequest.Builder builder =
-                    mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
-            mRequest = builder.build();
-            mRequest.start();
-            mCallback.blockForDone();
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
 
-            quicWasUsed = isQuic(mCallback.mResponseInfo.getNegotiatedProtocol());
-            if (quicWasUsed) {
-                break;
-            }
-        }
-
-        assertTrue(quicWasUsed);
-        // This tests uses a non-hermetic server. Instead of asserting, assume the next callback.
-        // This way, if the request were to fail, the test would just be skipped instead of failing.
-        assumeOKStatusCode(mCallback.mResponseInfo);
     }
 
     @Test
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 775c36f..18c2171 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -129,9 +129,6 @@
         // Tethered traffic will have the hop limit properly decremented.
         // Consequently, set the hoplimit greater by one than the upstream
         // unicast hop limit.
-        //
-        // TODO: Dynamically pass down the IPV6_UNICAST_HOPS value from the
-        // upstream interface for more correct behaviour.
         static final byte DEFAULT_HOPLIMIT = 65;
 
         public boolean hasDefaultRoute;
diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md
index b64667c..9d086de 100644
--- a/nearby/tests/multidevices/README.md
+++ b/nearby/tests/multidevices/README.md
@@ -43,14 +43,24 @@
 *   Adjust Bluetooth profile configurations. \
     The Fast Pair provider simulator is an opposite role to the seeker. It needs
     to enable/disable the following Bluetooth profile:
-    *   Disable A2DP (profile_supported_a2dp)
-    *   Disable the AVRCP controller (profile_supported_avrcp_controller)
-    *   Enable A2DP sink (profile_supported_a2dp_sink)
-    *   Enable the HFP client connection service (profile_supported_hfpclient,
-        hfp_client_connection_service_enabled)
-    *   Enable the AVRCP target (profile_supported_avrcp_target)
-    *   Enable the automatic audio focus request
-        (a2dp_sink_automatically_request_audio_focus)
+    *   Disable A2DP source (bluetooth.profile.a2dp.source.enabled)
+    *   Enable A2DP sink (bluetooth.profile.a2dp.sink.enabled)
+    *   Disable the AVRCP controller (bluetooth.profile.avrcp.controller.enabled)
+    *   Enable the AVRCP target (bluetooth.profile.avrcp.target.enabled)
+    *   Enable the HFP service (bluetooth.profile.hfp.ag.enabled, bluetooth.profile.hfp.hf.enabled)
+
+```makefile
+# The Bluetooth profiles that Fast Pair provider simulator expect to have enabled.
+PRODUCT_PRODUCT_PROPERTIES += \
+    bluetooth.device.default_name=FastPairProviderSimulator \
+    bluetooth.profile.a2dp.source.enabled=false \
+    bluetooth.profile.a2dp.sink.enabled=true \
+    bluetooth.profile.avrcp.controller.enabled=false \
+    bluetooth.profile.avrcp.target.enabled=true \
+    bluetooth.profile.hfp.ag.enabled=true \
+    bluetooth.profile.hfp.hf.enabled=true
+```
+
 *   Adjust Bluetooth TX power limitation in Bluetooth module and disable the
     Fast Pair in Google Play service (aka GMS)
 
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 961337d..c660792 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -946,7 +946,11 @@
     @GuardedBy("mStatsLock")
     private void shutdownLocked() {
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        try {
+            tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        } catch (IllegalStateException e) {
+            Log.i(TAG, "shutdownLocked: error when unregister tethering, ignored. e=" + e);
+        }
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mUserReceiver);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ba503e0..b449e72 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -244,6 +244,7 @@
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -310,11 +311,13 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -1485,6 +1488,18 @@
                 @NonNull final UserHandle user) {
             return CompatChanges.isChangeEnabled(changeId, packageName, user);
         }
+
+        /**
+         * Call {@link InetDiagMessage#destroyLiveTcpSockets(Set, Set)}
+         *
+         * @param ranges target uid ranges
+         * @param exemptUids uids to skip close socket
+         */
+        public void destroyLiveTcpSockets(@NonNull final Set<Range<Integer>> ranges,
+                @NonNull final Set<Integer> exemptUids)
+                throws SocketException, InterruptedIOException, ErrnoException {
+            InetDiagMessage.destroyLiveTcpSockets(ranges, exemptUids);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -7910,10 +7925,20 @@
         return SdkLevel.isAtLeastU() ? (networkHandle + ":" + iface) : ("iface:" + iface);
     }
 
+    private static boolean isWakeupMarkingSupported(NetworkCapabilities capabilities) {
+        if (capabilities.hasTransport(TRANSPORT_WIFI)) {
+            return true;
+        }
+        if (SdkLevel.isAtLeastU() && capabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return true;
+        }
+        return false;
+    }
+
     private void wakeupModifyInterface(String iface, NetworkAgentInfo nai, boolean add) {
         // Marks are only available on WiFi interfaces. Checking for
         // marks on unsupported interfaces is harmless.
-        if (!nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+        if (!isWakeupMarkingSupported(nai.networkCapabilities)) {
             return;
         }
 
@@ -8448,11 +8473,11 @@
         return stableRanges;
     }
 
-    private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges,
-            int[] exemptUids) {
+    private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
+            Set<Integer> exemptUids) {
         if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
             try {
-                mNetd.socketDestroy(ranges, exemptUids);
+                mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUids);
             } catch (Exception e) {
                 loge("Exception in socket destroy: ", e);
             }
@@ -8460,16 +8485,16 @@
     }
 
     private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
-        int[] exemptUids = new int[2];
+        final Set<Integer> exemptUids = new ArraySet<>();
         // TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
         // by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
         // starting a legacy VPN, and remove VPN_UID here. (b/176542831)
-        exemptUids[0] = VPN_UID;
-        exemptUids[1] = nai.networkCapabilities.getOwnerUid();
+        exemptUids.add(VPN_UID);
+        exemptUids.add(nai.networkCapabilities.getOwnerUid());
         UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
 
         // Close sockets before modifying uid ranges so that RST packets can reach to the server.
-        maybeCloseSockets(nai, ranges, exemptUids);
+        maybeCloseSockets(nai, uidRanges, exemptUids);
         try {
             if (add) {
                 mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -8483,7 +8508,7 @@
                     " on netId " + nai.network.netId + ". " + e);
         }
         // Close sockets that established connection while requesting netd.
-        maybeCloseSockets(nai, ranges, exemptUids);
+        maybeCloseSockets(nai, uidRanges, exemptUids);
     }
 
     private boolean isProxySetOnAnyDefaultNetwork() {
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
index 881c92d..ee8ab68 100644
--- a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -177,6 +177,7 @@
     private static final int MAX_EVENTS_LOGS = 40;
     private final LocalLog mEventLog = new LocalLog(MAX_EVENTS_LOGS);
 
+    private final KeepaliveStatsTracker mKeepaliveStatsTracker = new KeepaliveStatsTracker();
     /**
      * Information about a managed keepalive.
      *
@@ -421,6 +422,7 @@
     public void handleStartKeepalive(Message message) {
         final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj;
         mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
+        mKeepaliveStatsTracker.onStartKeepalive();
         mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
 
         // Add automatic on/off request into list to track its life cycle.
@@ -438,12 +440,14 @@
     }
 
     private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+        mKeepaliveStatsTracker.onResumeKeepalive();
         mKeepaliveTracker.handleStartKeepalive(ki);
         mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai);
     }
 
     private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
         mEventLog.log("Suspend keepalive " + ki.mCallback + " on " + ki.mNai);
+        mKeepaliveStatsTracker.onPauseKeepalive();
         // TODO : mKT.handleStopKeepalive should take a KeepaliveInfo instead
         mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), SUCCESS_PAUSED);
     }
@@ -467,6 +471,7 @@
 
     private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) {
         ensureRunningOnHandlerThread();
+        mKeepaliveStatsTracker.onStopKeepalive(autoKi.mAutomaticOnOffState != STATE_SUSPENDED);
         autoKi.close();
         if (null != autoKi.mAlarmListener) mAlarmManager.cancel(autoKi.mAlarmListener);
 
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
new file mode 100644
index 0000000..290d201
--- /dev/null
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.connectivity;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.metrics.DailykeepaliveInfoReported;
+import com.android.metrics.DurationForNumOfKeepalive;
+import com.android.metrics.DurationPerNumOfKeepalive;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// TODO(b/273451360): Also track KeepaliveLifetimeForCarrier and DailykeepaliveInfoReported
+/**
+ * Tracks carrier and duration metrics of automatic on/off keepalives.
+ *
+ * <p>This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs
+ * to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all
+ * public methods must be called by the same thread, namely the ConnectivityService handler thread.
+ */
+public class KeepaliveStatsTracker {
+    private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
+
+    private final Dependencies mDependencies;
+    // List of duration stats metric where the index is the number of concurrent keepalives.
+    // Each DurationForNumOfKeepalive message stores a registered duration and an active duration.
+    // Registered duration is the total time spent with mNumRegisteredKeepalive == index.
+    // Active duration is the total time spent with mNumActiveKeepalive == index.
+    private final List<DurationForNumOfKeepalive.Builder> mDurationPerNumOfKeepalive =
+            new ArrayList<>();
+
+    private int mNumRegisteredKeepalive = 0;
+    private int mNumActiveKeepalive = 0;
+
+    // A timestamp of the most recent time the duration metrics was updated.
+    private long mTimestampSinceLastUpdateDurations;
+
+    /** Dependency class */
+    @VisibleForTesting
+    public static class Dependencies {
+        // Returns a timestamp with the time base of SystemClock.uptimeMillis to keep durations
+        // relative to start time and avoid timezone change.
+        public long getUptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    public KeepaliveStatsTracker() {
+        this(new Dependencies());
+    }
+
+    @VisibleForTesting
+    public KeepaliveStatsTracker(Dependencies dependencies) {
+        mDependencies = dependencies;
+        mTimestampSinceLastUpdateDurations = mDependencies.getUptimeMillis();
+    }
+
+    /** Ensures the list of duration metrics is large enough for number of registered keepalives. */
+    private void ensureDurationPerNumOfKeepaliveSize() {
+        if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) {
+            throw new IllegalStateException(
+                    "Number of active or registered keepalives is negative");
+        }
+        if (mNumActiveKeepalive > mNumRegisteredKeepalive) {
+            throw new IllegalStateException(
+                    "Number of active keepalives greater than registered keepalives");
+        }
+
+        while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) {
+            final DurationForNumOfKeepalive.Builder durationForNumOfKeepalive =
+                    DurationForNumOfKeepalive.newBuilder();
+            durationForNumOfKeepalive.setNumOfKeepalive(mDurationPerNumOfKeepalive.size());
+            durationForNumOfKeepalive.setKeepaliveRegisteredDurationsMsec(0);
+            durationForNumOfKeepalive.setKeepaliveActiveDurationsMsec(0);
+
+            mDurationPerNumOfKeepalive.add(durationForNumOfKeepalive);
+        }
+    }
+
+    /**
+     * Updates the durations metrics to the given time. This should always be called before making a
+     * change to mNumRegisteredKeepalive or mNumActiveKeepalive to keep the duration metrics
+     * correct.
+     *
+     * @param timeNow a timestamp obtained using Dependencies.getUptimeMillis
+     */
+    private void updateDurationsPerNumOfKeepalive(long timeNow) {
+        if (mDurationPerNumOfKeepalive.size() < mNumRegisteredKeepalive) {
+            Log.e(TAG, "Unexpected jump in number of registered keepalive");
+        }
+        ensureDurationPerNumOfKeepaliveSize();
+
+        final int durationIncrease = (int) (timeNow - mTimestampSinceLastUpdateDurations);
+        final DurationForNumOfKeepalive.Builder durationForNumOfRegisteredKeepalive =
+                mDurationPerNumOfKeepalive.get(mNumRegisteredKeepalive);
+
+        durationForNumOfRegisteredKeepalive.setKeepaliveRegisteredDurationsMsec(
+                durationForNumOfRegisteredKeepalive.getKeepaliveRegisteredDurationsMsec()
+                        + durationIncrease);
+
+        final DurationForNumOfKeepalive.Builder durationForNumOfActiveKeepalive =
+                mDurationPerNumOfKeepalive.get(mNumActiveKeepalive);
+
+        durationForNumOfActiveKeepalive.setKeepaliveActiveDurationsMsec(
+                durationForNumOfActiveKeepalive.getKeepaliveActiveDurationsMsec()
+                        + durationIncrease);
+
+        mTimestampSinceLastUpdateDurations = timeNow;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just started and is active. */
+    public void onStartKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumRegisteredKeepalive++;
+        mNumActiveKeepalive++;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been paused. */
+    public void onPauseKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumActiveKeepalive--;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */
+    public void onResumeKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumActiveKeepalive++;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */
+    public void onStopKeepalive(boolean wasActive) {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumRegisteredKeepalive--;
+        if (wasActive) mNumActiveKeepalive--;
+    }
+
+    /**
+     * Builds and returns DailykeepaliveInfoReported proto.
+     */
+    public DailykeepaliveInfoReported buildKeepaliveMetrics() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        final DurationPerNumOfKeepalive.Builder durationPerNumOfKeepalive =
+                DurationPerNumOfKeepalive.newBuilder();
+
+        mDurationPerNumOfKeepalive.forEach(
+                durationForNumOfKeepalive ->
+                        durationPerNumOfKeepalive.addDurationForNumOfKeepalive(
+                                durationForNumOfKeepalive));
+
+        final DailykeepaliveInfoReported.Builder dailyKeepaliveInfoReported =
+                DailykeepaliveInfoReported.newBuilder();
+
+        // TODO(b/273451360): fill all the other values and write to ConnectivityStatsLog.
+        dailyKeepaliveInfoReported.setDurationPerNumOfKeepalive(durationPerNumOfKeepalive);
+
+        return dailyKeepaliveInfoReported.build();
+    }
+
+    /** Resets the stored metrics but maintains the state of keepalives */
+    public void resetMetrics() {
+        mDurationPerNumOfKeepalive.clear();
+        ensureDurationPerNumOfKeepaliveSize();
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index c28ee64..624acd3 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -154,7 +154,6 @@
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
@@ -809,26 +808,12 @@
                 mOldPrivateDnsSpecifier);
     }
 
-    // TODO: replace with CtsNetUtils.awaitPrivateDnsSetting in Q or above.
     private void expectPrivateDnsHostname(final String hostname) throws Exception {
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .build();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final NetworkCallback callback = new NetworkCallback() {
-            @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-                if (network.equals(mNetwork) &&
-                        Objects.equals(lp.getPrivateDnsServerName(), hostname)) {
-                    latch.countDown();
-                }
-            }
-        };
-
-        registerNetworkCallback(request, callback);
-
-        assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
-                latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
+            // Wait for private DNS setting to propagate.
+            mCtsNetUtils.awaitPrivateDnsSetting("Test wait private DNS setting timeout",
+                    network, hostname, false);
+        }
     }
 
     private void setAndVerifyPrivateDns(boolean strictMode) throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 9d234d3..b380d02 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2112,7 +2112,12 @@
     @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
     @Test
     public void testSetAirplaneMode() throws Exception{
-        final boolean supportWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI);
+        // Starting from T, wifi supports airplane mode enhancement which may not disconnect wifi
+        // when airplane mode is on. The actual behavior that the device will have could only be
+        // checked with hidden wifi APIs(see Settings.Secure.WIFI_APM_STATE). Thus, stop verifying
+        // wifi on T+ device.
+        final boolean verifyWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && !SdkLevel.isAtLeastT();
         final boolean supportTelephony = mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
         // store the current state of airplane mode
         final boolean isAirplaneModeEnabled = isAirplaneModeEnabled();
@@ -2123,7 +2128,7 @@
         // Verify that networks are available as expected if wifi or cell is supported. Continue the
         // test if none of them are supported since test should still able to verify the permission
         // mechanism.
-        if (supportWifi) {
+        if (verifyWifi) {
             mCtsNetUtils.ensureWifiConnected();
             registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
         }
@@ -2147,7 +2152,7 @@
             // Verify that the enabling airplane mode takes effect as expected to prevent flakiness
             // caused by fast airplane mode switches. Ensure network lost before turning off
             // airplane mode.
-            if (supportWifi) waitForLost(wifiCb);
+            if (verifyWifi) waitForLost(wifiCb);
             if (supportTelephony) waitForLost(telephonyCb);
 
             // Verify we can disable Airplane Mode with correct permission:
@@ -2156,7 +2161,7 @@
             // Verify that turning airplane mode off takes effect as expected.
             // connectToCell only registers a request, it cannot / does not need to be called twice
             mCtsNetUtils.ensureWifiConnected();
-            if (supportWifi) waitForAvailable(wifiCb);
+            if (verifyWifi) waitForAvailable(wifiCb);
             if (supportTelephony) waitForAvailable(telephonyCb);
         } finally {
             // Restore the previous state of airplane mode and permissions:
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 691ab99..17a9ca2 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -18,21 +18,18 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkUtils;
 import android.net.cts.util.CtsNetUtils;
 import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.test.AndroidTestCase;
 
-import java.util.ArrayList;
-
 public class MultinetworkApiTest extends AndroidTestCase {
 
     static {
@@ -75,26 +72,8 @@
         super.tearDown();
     }
 
-    private Network[] getTestableNetworks() {
-        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
-        for (Network network : mCM.getAllNetworks()) {
-            final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
-            if (nc != null
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                testableNetworks.add(network);
-            }
-        }
-
-        assertTrue(
-                "This test requires that at least one network be connected. " +
-                "Please ensure that the device is connected to a network.",
-                testableNetworks.size() >= 1);
-        return testableNetworks.toArray(new Network[0]);
-    }
-
     public void testGetaddrinfo() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runGetaddrinfoCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -109,7 +88,7 @@
         assertNull(mCM.getProcessDefaultNetwork());
         assertEquals(0, NetworkUtils.getBoundNetworkForProcess());
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             mCM.setProcessDefaultNetwork(null);
             assertNull(mCM.getProcessDefaultNetwork());
 
@@ -128,7 +107,7 @@
             mCM.setProcessDefaultNetwork(null);
         }
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             NetworkUtils.bindProcessToNetwork(0);
             assertNull(mCM.getBoundNetworkForProcess());
 
@@ -148,7 +127,7 @@
 
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testSetsocknetwork() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runSetsocknetwork(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -158,7 +137,7 @@
     }
 
     public void testNativeDatagramTransmission() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runDatagramCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -181,7 +160,7 @@
 
     public void testNetworkHandle() {
         // Test Network -> NetworkHandle -> Network results in the same Network.
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             long networkHandle = network.getNetworkHandle();
             Network newNetwork = Network.fromNetworkHandle(networkHandle);
             assertEquals(newNetwork, network);
@@ -203,7 +182,7 @@
     }
 
     public void testResNApi() throws Exception {
-        final Network[] testNetworks = getTestableNetworks();
+        final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
 
         for (Network network : testNetworks) {
             // Throws AssertionError directly in jni function if test fail.
@@ -229,7 +208,7 @@
         // b/144521720
         try {
             mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
-            for (Network network : getTestableNetworks()) {
+            for (Network network : mCtsNetUtils.getTestableNetworks()) {
               // Wait for private DNS setting to propagate.
               mCtsNetUtils.awaitPrivateDnsSetting("NxDomain test wait private DNS setting timeout",
                         network, GOOGLE_PRIVATE_DNS_SERVER, true);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index b535a8f..869562b 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -108,17 +108,6 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.assertThrows
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.timeout
-import org.mockito.Mockito.verify
 import java.io.Closeable
 import java.io.IOException
 import java.net.DatagramSocket
@@ -136,6 +125,17 @@
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
 
 // This test doesn't really have a constraint on how fast the methods should return. If it's
 // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
index f0c87673..4854901 100644
--- a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -23,12 +23,14 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.PacProxyManager;
@@ -150,6 +152,9 @@
     @AppModeFull(reason = "Instant apps can't bind sockets to localhost for a test proxy server")
     @Test
     public void testSetCurrentProxyScriptUrl() throws Exception {
+        // Devices without WebView/JavaScript cannot support PAC proxies
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW));
+
         // Register a PacProxyInstalledListener
         final TestPacProxyInstalledListener listener = new TestPacProxyInstalledListener();
         final Executor executor = (Runnable r) -> r.run();
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index d817630..0c4f794 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -57,6 +57,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
@@ -68,6 +70,8 @@
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -506,17 +510,18 @@
      * @throws InterruptedException If the thread is interrupted.
      */
     public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
-            @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+            @Nullable String server, boolean requiresValidatedServer) throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
-        NetworkCallback callback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             @Override
             public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
                 Log.i(TAG, "Link properties of network " + n + " changed to " + lp);
                 if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
                     return;
                 }
-                if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
+                Log.i(TAG, "Set private DNS server to " + server);
+                if (network.equals(n) && Objects.equals(server, lp.getPrivateDnsServerName())) {
                     latch.countDown();
                 }
             }
@@ -539,6 +544,27 @@
     }
 
     /**
+     * Get all testable Networks with internet capability.
+     */
+    public Network[] getTestableNetworks() {
+        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
+        for (Network network : mCm.getAllNetworks()) {
+            final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+            if (nc != null
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                testableNetworks.add(network);
+            }
+        }
+
+        assertTrue("This test requires that at least one public Internet-providing"
+                        + " network be connected. Please ensure that the device is connected to"
+                        + " a network.",
+                testableNetworks.size() >= 1);
+        return testableNetworks.toArray(new Network[0]);
+    }
+
+    /**
      * Receiver that captures the last connectivity change's network type and state. Recognizes
      * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
      */
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 36b3356..8b286a0 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,7 +26,6 @@
         "libandroid_net_frameworktests_util_jni",
         "libbase",
         "libbinder",
-        "libbpf_bcc",
         "libc++",
         "libcrypto",
         "libcutils",
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 1cc0c89..bd8ac06 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -502,7 +502,7 @@
     // complete before callbacks are verified.
     private static final int TEST_REQUEST_TIMEOUT_MS = 150;
 
-    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
+    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 2_000;
 
     private static final long TIMESTAMP = 1234L;
 
@@ -1853,7 +1853,7 @@
         final Context mockResContext = mock(Context.class);
         doReturn(mResources).when(mockResContext).getResources();
         ConnectivityResources.setResourcesContextForTest(mockResContext);
-        mDeps = new ConnectivityServiceDependencies(mockResContext);
+        mDeps = spy(new ConnectivityServiceDependencies(mockResContext));
         mAutoOnOffKeepaliveDependencies =
                 new AutomaticOnOffKeepaliveTrackerDependencies(mServiceContext);
         mService = new ConnectivityService(mServiceContext,
@@ -1912,7 +1912,8 @@
                 .getBoolean(R.bool.config_cellular_radio_timesharing_capable);
     }
 
-    class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
+    // ConnectivityServiceDependencies is public to use Mockito.spy
+    public class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
         final ConnectivityResources mConnRes;
 
         ConnectivityServiceDependencies(final Context mockResContext) {
@@ -2148,6 +2149,12 @@
                 }
             }
         }
+
+        @Override
+        public void destroyLiveTcpSockets(final Set<Range<Integer>> ranges,
+                final Set<Integer> exemptUids) {
+            // This function is empty since the invocation of this method is verified by mocks
+        }
     }
 
     private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -3369,8 +3376,10 @@
         // This test would be flaky with the default 120ms timer: that is short enough that
         // lingered networks are torn down before assertions can be run. We don't want to mock the
         // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful
-        // in detecting races.
-        mService.mLingerDelayMs = 300;
+        // in detecting races. Furthermore, sometimes the test is running while Phenotype is running
+        // so hot that the test doesn't get the CPU for multiple hundreds of milliseconds, so this
+        // needs to be suitably long.
+        mService.mLingerDelayMs = 2_000;
 
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
@@ -12469,12 +12478,11 @@
 
     private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid)
             throws Exception {
-        InOrder inOrder = inOrder(mMockNetd);
-        ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
+        InOrder inOrder = inOrder(mMockNetd, mDeps);
+        final Set<Integer> exemptUidSet = new ArraySet<>(List.of(exemptUid, Process.VPN_UID));
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDeps).destroyLiveTcpSockets(UidRange.toIntRanges(vpnRanges),
+                exemptUidSet);
 
         if (add) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(
@@ -12486,9 +12494,8 @@
                             toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_VPN));
         }
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDeps).destroyLiveTcpSockets(UidRange.toIntRanges(vpnRanges),
+                exemptUidSet);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
new file mode 100644
index 0000000..d262255
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.metrics.DailykeepaliveInfoReported;
+import com.android.metrics.DurationForNumOfKeepalive;
+import com.android.metrics.DurationPerNumOfKeepalive;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+public class KeepaliveStatsTrackerTest {
+    private static final int TEST_UID = 1234;
+
+    private KeepaliveStatsTracker mKeepaliveStatsTracker;
+    @Mock KeepaliveStatsTracker.Dependencies mDependencies;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        setUptimeMillis(0);
+        mKeepaliveStatsTracker = new KeepaliveStatsTracker(mDependencies);
+    }
+
+    private void setUptimeMillis(long time) {
+        doReturn(time).when(mDependencies).getUptimeMillis();
+    }
+
+    /**
+     * Asserts that a DurationPerNumOfKeepalive contains expected values
+     *
+     * @param expectRegisteredDurations integer array where the index is the number of concurrent
+     *     keepalives and the value is the expected duration of time that the tracker is in a state
+     *     with the given number of keepalives registered.
+     * @param expectActiveDurations integer array where the index is the number of concurrent
+     *     keepalives and the value is the expected duration of time that the tracker is in a state
+     *     with the given number of keepalives active.
+     * @param resultDurationsPerNumOfKeepalive the DurationPerNumOfKeepalive message to assert.
+     */
+    private void assertDurationMetrics(
+            int[] expectRegisteredDurations,
+            int[] expectActiveDurations,
+            DurationPerNumOfKeepalive resultDurationsPerNumOfKeepalive) {
+        final int maxNumOfKeepalive = expectRegisteredDurations.length;
+        assertEquals(maxNumOfKeepalive, expectActiveDurations.length);
+        assertEquals(
+                maxNumOfKeepalive,
+                resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepaliveCount());
+        for (int numOfKeepalive = 0; numOfKeepalive < maxNumOfKeepalive; numOfKeepalive++) {
+            final DurationForNumOfKeepalive resultDurations =
+                    resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepalive(numOfKeepalive);
+
+            assertEquals(numOfKeepalive, resultDurations.getNumOfKeepalive());
+            assertEquals(
+                    expectRegisteredDurations[numOfKeepalive],
+                    resultDurations.getKeepaliveRegisteredDurationsMsec());
+            assertEquals(
+                    expectActiveDurations[numOfKeepalive],
+                    resultDurations.getKeepaliveActiveDurationsMsec());
+        }
+    }
+
+    private void assertDailyKeepaliveInfoReported(
+            DailykeepaliveInfoReported dailyKeepaliveInfoReported,
+            int[] expectRegisteredDurations,
+            int[] expectActiveDurations) {
+        // TODO(b/273451360) Assert these values when they are filled.
+        assertFalse(dailyKeepaliveInfoReported.hasKeepaliveLifetimePerCarrier());
+        assertFalse(dailyKeepaliveInfoReported.hasKeepaliveRequests());
+        assertFalse(dailyKeepaliveInfoReported.hasAutomaticKeepaliveRequests());
+        assertFalse(dailyKeepaliveInfoReported.hasDistinctUserCount());
+        assertTrue(dailyKeepaliveInfoReported.getUidList().isEmpty());
+
+        final DurationPerNumOfKeepalive resultDurations =
+                dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive();
+        assertDurationMetrics(expectRegisteredDurations, expectActiveDurations, resultDurations);
+    }
+
+    @Test
+    public void testNoKeepalive() {
+        final int writeTime = 5000;
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // Expect that the durations are all in numOfKeepalive = 0.
+        final int[] expectRegisteredDurations = new int[] {writeTime};
+        final int[] expectActiveDurations = new int[] {writeTime};
+
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S                          W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_startOnly() {
+        final int startTime = 1000;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is never stopped, expect the duration for numberOfKeepalive of 1 to range
+        // from startTime to writeTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P                  W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_paused() {
+        final int startTime = 1000;
+        final int pauseTime = 2030;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is paused but not stopped, expect the registered duration for
+        // numberOfKeepalive of 1 to still range from startTime to writeTime while the active
+        // duration stops at pauseTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {startTime + (writeTime - pauseTime), pauseTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P        R         W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_resumed() {
+        final int startTime = 1000;
+        final int pauseTime = 2030;
+        final int resumeTime = 3450;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is paused and resumed but not stopped, expect the registered duration for
+        // numberOfKeepalive of 1 to still range from startTime to writeTime while the active
+        // duration stops at pauseTime but resumes at resumeTime and stops at writeTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + (resumeTime - pauseTime),
+                    (pauseTime - startTime) + (writeTime - resumeTime)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P      R     S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_stopped() {
+        final int startTime = 1000;
+        final int pauseTime = 2930;
+        final int resumeTime = 3452;
+        final int stopTime = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is now stopped, expect the registered duration for numberOfKeepalive of 1
+        // to now range from startTime to stopTime while the active duration stops at pauseTime but
+        // resumes at resumeTime and stops again at stopTime.
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + (resumeTime - pauseTime) + (writeTime - stopTime),
+                    (pauseTime - startTime) + (stopTime - resumeTime)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P            S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_pausedStopped() {
+        final int startTime = 1000;
+        final int pauseTime = 2930;
+        final int stopTime = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ false);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is stopped while paused, expect the registered duration for
+        // numberOfKeepalive of 1 to range from startTime to stopTime while the active duration
+        // simply stops at pauseTime.
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {startTime + (writeTime - pauseTime), (pauseTime - startTime)};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S  P R P R P R       S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_multiplePauses() {
+        final int startTime = 1000;
+        // Alternating timestamps of pause and resume
+        final int[] pauseResumeTimes = new int[] {1200, 1400, 1700, 2000, 2400, 2800};
+        final int stopTime = 4000;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        for (int i = 0; i < pauseResumeTimes.length; i++) {
+            setUptimeMillis(pauseResumeTimes[i]);
+            if (i % 2 == 0) {
+                mKeepaliveStatsTracker.onPauseKeepalive();
+            } else {
+                mKeepaliveStatsTracker.onResumeKeepalive();
+            }
+        }
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + /* sum of (Resume - Pause) */ (900) + (writeTime - stopTime),
+                    (pauseResumeTimes[0] - startTime)
+                            + /* sum of (Pause - Resume) */ (700)
+                            + (stopTime - pauseResumeTimes[5])
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive1    S1  P1     R1         S1    W
+     * Keepalive2           S2     P2   R2       W
+     * Timeline   |------------------------------|
+     */
+    @Test
+    public void testTwoKeepalives() {
+        // The suffix 1/2 indicates which keepalive it is referring to.
+        final int startTime1 = 1000;
+        final int pauseTime1 = 1500;
+        final int startTime2 = 2000;
+        final int resumeTime1 = 2500;
+        final int pauseTime2 = 3000;
+        final int resumeTime2 = 3500;
+        final int stopTime1 = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime1);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime1);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(startTime2);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(resumeTime1);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(pauseTime2);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime2);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(stopTime1);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // With two keepalives, the number of concurrent keepalives can vary from 0-2 depending on
+        // both keepalive states.
+        final int[] expectRegisteredDurations =
+                new int[] {
+                    startTime1,
+                    // 1 registered keepalive before keepalive2 starts and after keepalive1 stops.
+                    (startTime2 - startTime1) + (writeTime - stopTime1),
+                    // 2 registered keepalives between keepalive2 start and keepalive1 stop.
+                    stopTime1 - startTime2
+                };
+
+        final int[] expectActiveDurations =
+                new int[] {
+                    // 0 active keepalives when keepalive1 is paused before keepalive2 starts.
+                    startTime1 + (startTime2 - pauseTime1),
+                    // 1 active keepalive before keepalive1 is paused.
+                    (pauseTime1 - startTime1)
+                            // before keepalive1 is resumed and after keepalive2 starts.
+                            + (resumeTime1 - startTime2)
+                            // during keepalive2 is paused since keepalive1 has been resumed.
+                            + (resumeTime2 - pauseTime2)
+                            // after keepalive1 stops since keepalive2 has been resumed.
+                            + (writeTime - stopTime1),
+                    // 2 active keepalives before keepalive2 is paused and before keepalive1 stops.
+                    (pauseTime2 - resumeTime1) + (stopTime1 - resumeTime2)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S   W(reset+W)         S    W
+     * Timeline   |------------------------------|
+     */
+    @Test
+    public void testResetMetrics() {
+        final int startTime = 1000;
+        final int writeTime = 5000;
+        final int stopTime = 7000;
+        final int writeTime2 = 10000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // Same expect as testOneKeepalive_startOnly
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+
+        // Reset metrics
+        mKeepaliveStatsTracker.resetMetrics();
+
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported2 =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+        // Expect the stored durations to be 0 but still contain the number of keepalive = 1.
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported2,
+                /* expectRegisteredDurations= */ new int[] {0, 0},
+                /* expectActiveDurations= */ new int[] {0, 0});
+
+        // Expect that the keepalive is still registered after resetting so it can be stopped.
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime2);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported3 =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        final int[] expectRegisteredDurations2 =
+                new int[] {writeTime2 - stopTime, stopTime - writeTime};
+        final int[] expectActiveDurations2 =
+                new int[] {writeTime2 - stopTime, stopTime - writeTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported3,
+                expectRegisteredDurations2,
+                expectActiveDurations2);
+    }
+}