Merge "Address comment from ag/12107302" into main
diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
index ece4a34..464862d 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -27,12 +27,14 @@
 import androidx.test.core.app.ApplicationProvider
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SkipPresubmit
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlin.test.assertEquals
 import org.hamcrest.MatcherAssert
 import org.hamcrest.Matchers
 import org.junit.After
+import org.junit.AssumptionViolatedException
 import org.junit.Before
 import org.junit.runner.RunWith
 
@@ -67,10 +69,21 @@
 
     @Test
     @Throws(Exception::class)
+    @SkipPresubmit(reason = "b/293141085 Confirm non-flaky and move to presubmit after SLO")
     fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
         stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
         stream!!.start()
-        callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+        // We call to a real server and hence the server may not be reachable, cancel this stream
+        // and rethrow the exception before tearDown,
+        // otherwise shutdown would fail with active request error.
+        try {
+            callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+        } catch (e: AssumptionViolatedException) {
+            stream!!.cancel()
+            callback.blockForDone()
+            throw e
+        }
+
         val info = callback.mResponseInfo
         assumeOKStatusCode(info)
         MatcherAssert.assertThat(
@@ -185,5 +198,4 @@
         stream = builder.build()
         assertThat(stream!!.isDelayRequestHeadersUntilFirstFlushEnabled()).isTrue()
     }
-
 }
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
index a3e86de..e8fd39b 100644
--- a/Cronet/tests/mts/jarjar_excludes.txt
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -1,5 +1,8 @@
-# It's prohibited to jarjar androidx packages
+# jarjar-gen can't handle some kotlin object expression, exclude packages that include them
 androidx\..+
+kotlin\.test\..+
+kotlin\.reflect\..+
+org\.mockito\..+
 # Do not jarjar the api classes
 android\.net\..+
 # cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 0326bf2..76e4af8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -5,7 +5,12 @@
     },
     {
       // In addition to ConnectivityCoverageTests, runs non-connectivity-module tests
-      "name": "FrameworksNetTests"
+      "name": "FrameworksNetTests",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ]
     },
     // Run in addition to mainline-presubmit as mainline-presubmit is not
     // supported in every branch.
@@ -100,6 +105,9 @@
       "name": "NetHttpCoverageTests",
       "options": [
         {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
           // These sometimes take longer than 1 min which is the presubmit timeout
           "exclude-annotation": "androidx.test.filters.LargeTest"
         }
@@ -120,6 +128,18 @@
     },
     {
       "name": "FrameworksNetDeflakeTest"
+    },
+    // Postsubmit on virtual devices to monitor flakiness of @SkipPresubmit methods
+    {
+      "name": "CtsNetTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
+      "name": "FrameworksNetTests"
     }
   ],
   "mainline-presubmit": [
@@ -130,6 +150,9 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
         }
       ]
@@ -141,6 +164,9 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
         }
       ]
@@ -152,6 +178,9 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
         }
       ]
@@ -163,6 +192,9 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
         }
       ]
@@ -176,10 +208,16 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
         },
         {
           "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
         }
       ]
     },
@@ -194,7 +232,13 @@
           "exclude-annotation": "com.android.testutils.SkipPresubmit"
         },
         {
+          "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit"
+        },
+        {
           "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.DnsResolverModuleTest"
         }
       ]
     },
@@ -220,6 +264,9 @@
       "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
       "options": [
         {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
           // These sometimes take longer than 1 min which is the presubmit timeout
           "exclude-annotation": "androidx.test.filters.LargeTest"
         }
@@ -248,6 +295,15 @@
           "exclude-annotation": "com.android.testutils.NetworkStackModuleTest"
         }
       ]
+    },
+    // Postsubmit on virtual devices to monitor flakiness of @SkipMainlinePresubmit methods
+    {
+      "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
     }
   ],
   "imports": [
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 905b8fa..a104084 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -138,10 +138,11 @@
     }
 
     switch (proto) {
-        case IPPROTO_TCP:  // For TCP & UDP the checksum neutrality of the chosen IPv6
-        case IPPROTO_UDP:  // address means there is no need to update their checksums.
-        case IPPROTO_GRE:  // We do not need to bother looking at GRE/ESP headers,
-        case IPPROTO_ESP:  // since there is never a checksum to update.
+        case IPPROTO_TCP:      // For TCP, UDP & UDPLITE the checksum neutrality of the chosen
+        case IPPROTO_UDP:      // IPv6 address means there is no need to update their checksums.
+        case IPPROTO_UDPLITE:  //
+        case IPPROTO_GRE:      // We do not need to bother looking at GRE/ESP headers,
+        case IPPROTO_ESP:      // since there is never a checksum to update.
             break;
 
         default:  // do not know how to handle anything else
@@ -328,12 +329,13 @@
     if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE;
 
     switch (ip4->protocol) {
-        case IPPROTO_TCP:  // For TCP & UDP the checksum neutrality of the chosen IPv6
-        case IPPROTO_GRE:  // address means there is no need to update their checksums.
-        case IPPROTO_ESP:  // We do not need to bother looking at GRE/ESP headers,
-            break;         // since there is never a checksum to update.
+        case IPPROTO_TCP:      // For TCP, UDP & UDPLITE the checksum neutrality of the chosen
+        case IPPROTO_UDPLITE:  // IPv6 address means there is no need to update their checksums.
+        case IPPROTO_GRE:      // We do not need to bother looking at GRE/ESP headers,
+        case IPPROTO_ESP:      // since there is never a checksum to update.
+            break;
 
-        case IPPROTO_UDP:  // See above comment, but must also have UDP header...
+        case IPPROTO_UDP:      // See above comment, but must also have UDP header...
             if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE;
             const struct udphdr* uh = (const struct udphdr*)(ip4 + 1);
             // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the
diff --git a/remote_auth/OWNERS b/remote_auth/OWNERS
new file mode 100644
index 0000000..25a32b9
--- /dev/null
+++ b/remote_auth/OWNERS
@@ -0,0 +1,14 @@
+# Bug component: 1145231
+# Bug template url: http://b/new?component=1145231&template=1715387
+billyhuang@google.com
+boetger@google.com
+casbor@google.com
+derekjedral@google.com
+dlm@google.com
+igorzas@google.com
+jacobhobbie@google.com
+jasonsun@google.com
+jianbing@google.com
+jinjiechen@google.com
+justinmcclain@google.com
+salilr@google.com
diff --git a/remote_auth/README.md b/remote_auth/README.md
new file mode 100644
index 0000000..384fcf7
--- /dev/null
+++ b/remote_auth/README.md
@@ -0,0 +1,47 @@
+# RemoteAuth Mainline Module
+
+This directory contains code for the RemoteAuth module.
+
+## Directory Structure
+
+`framework`
+ - Contains client side APIs and AIDL files.
+
+`jni`
+ - JNI wrapper for invoking Android APIs from native code.
+
+`native`
+ - Native code implementation for RemoteAuth module services.
+
+`service`
+ - Server side implementation for RemoteAuth module services.
+
+`tests`
+ - Unit/Multi devices tests for RemoteAuth module (both Java and native code).
+
+## IDE setup
+
+### AIDEGen
+
+AIDEGen is deprecated, prefer ASfP [go/asfp](http://go/asfp)
+```sh
+$ source build/envsetup.sh && lunch <TARGET>
+$ cd packages/modules/Connectivity
+$ aidegen .
+# This will launch Intellij project for RemoteAuth module.
+```
+
+### ASfP
+
+See full instructions for ASfP at [go/asfp-getting-started](http://go/asfp-getting-started)
+
+## Build and Install
+
+```sh
+$ source build/envsetup.sh && lunch <TARGET>
+$ m com.google.android.tethering deapexer
+$ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \
+    ${ANDROID_PRODUCT_OUT}/system/apex/com.google.android.tethering.capex \
+    --output /tmp/tethering.apex
+$ adb install -r /tmp/tethering.apex
+```
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 6c8e273..bcedbef 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -16,6 +16,14 @@
 
 package com.android.metrics;
 
+import static com.android.metrics.NetworkNsdReported.Builder;
+
+import android.stats.connectivity.MdnsQueryResult;
+import android.stats.connectivity.NsdEventType;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ConnectivityStatsLog;
+
 /**
  * Class to record the NetworkNsdReported into statsd. Each client should create this class to
  * report its data.
@@ -25,11 +33,93 @@
     private final boolean mIsLegacy;
     // The client id.
     private final int mClientId;
+    private final Dependencies mDependencies;
 
     public NetworkNsdReportedMetrics(boolean isLegacy, int clientId) {
-        mIsLegacy = isLegacy;
-        mClientId = clientId;
+        this(isLegacy, clientId, new Dependencies());
     }
 
-    // TODO: Report metrics data.
+    @VisibleForTesting
+    NetworkNsdReportedMetrics(boolean isLegacy, int clientId, Dependencies dependencies) {
+        mIsLegacy = isLegacy;
+        mClientId = clientId;
+        mDependencies = dependencies;
+    }
+
+    /**
+     * Dependencies of NetworkNsdReportedMetrics, for injection in tests.
+     */
+    public static class Dependencies {
+
+        /**
+         * @see ConnectivityStatsLog
+         */
+        public void statsWrite(NetworkNsdReported event) {
+            ConnectivityStatsLog.write(ConnectivityStatsLog.NETWORK_NSD_REPORTED,
+                    event.getIsLegacy(),
+                    event.getClientId(),
+                    event.getTransactionId(),
+                    event.getIsKnownService(),
+                    event.getType().getNumber(),
+                    event.getEventDurationMillisec(),
+                    event.getQueryResult().getNumber(),
+                    event.getFoundServiceCount(),
+                    event.getFoundCallbackCount(),
+                    event.getLostCallbackCount(),
+                    event.getRepliedRequestsCount());
+        }
+    }
+
+    private Builder makeReportedBuilder() {
+        final Builder builder = NetworkNsdReported.newBuilder();
+        builder.setIsLegacy(mIsLegacy);
+        builder.setClientId(mClientId);
+        return builder;
+    }
+
+    /**
+     * Report service registration succeeded metric data.
+     *
+     * @param transactionId The transaction id of service registration.
+     * @param durationMs The duration of service registration success.
+     */
+    public void reportServiceRegistrationSucceeded(int transactionId, long durationMs) {
+        final Builder builder = makeReportedBuilder();
+        builder.setTransactionId(transactionId);
+        builder.setType(NsdEventType.NET_REGISTER);
+        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTERED);
+        builder.setEventDurationMillisec(durationMs);
+        mDependencies.statsWrite(builder.build());
+    }
+
+    /**
+     * Report service registration failed metric data.
+     *
+     * @param transactionId The transaction id of service registration.
+     * @param durationMs The duration of service registration failed.
+     */
+    public void reportServiceRegistrationFailed(int transactionId, long durationMs) {
+        final Builder builder = makeReportedBuilder();
+        builder.setTransactionId(transactionId);
+        builder.setType(NsdEventType.NET_REGISTER);
+        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED);
+        builder.setEventDurationMillisec(durationMs);
+        mDependencies.statsWrite(builder.build());
+    }
+
+    /**
+     * Report service unregistration success metric data.
+     *
+     * @param transactionId The transaction id of service registration.
+     * @param durationMs The duration of service stayed registered.
+     */
+    public void reportServiceUnregistration(int transactionId, long durationMs) {
+        final Builder builder = makeReportedBuilder();
+        builder.setTransactionId(transactionId);
+        builder.setType(NsdEventType.NET_REGISTER);
+        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_UNREGISTERED);
+        builder.setEventDurationMillisec(durationMs);
+        // TODO: Report repliedRequestsCount
+        mDependencies.statsWrite(builder.build());
+    }
 }
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 95717c2..745c5bc 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -162,6 +162,7 @@
     public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     private static final long CLEANUP_DELAY_MS = 10000;
     private static final int IFACE_IDX_ANY = 0;
+    private static final int NO_TRANSACTION = -1;
     private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
 
     private final Context mContext;
@@ -178,6 +179,8 @@
     private final MdnsSocketProvider mMdnsSocketProvider;
     @NonNull
     private final MdnsAdvertiser mAdvertiser;
+    @NonNull
+    private final Clock mClock;
     private final SharedLog mServiceLogs = LOGGER.forSubComponent(TAG);
     // WARNING : Accessing these values in any thread is not safe, it must only be changed in the
     // state machine thread. If change this outside state machine, it will need to introduce
@@ -530,8 +533,9 @@
                         try {
                             cb.asBinder().linkToDeath(arg.connector, 0);
                             final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
-                            final NetworkNsdReportedMetrics metrics = new NetworkNsdReportedMetrics(
-                                    !arg.useJavaBackend, (int) new Clock().elapsedRealtime());
+                            final NetworkNsdReportedMetrics metrics =
+                                    mDeps.makeNetworkNsdReportedMetrics(
+                                            !arg.useJavaBackend, (int) mClock.elapsedRealtime());
                             cInfo = new ClientInfo(cb, arg.uid, arg.useJavaBackend,
                                     mServiceLogs.forSubComponent(tag), metrics);
                             mClients.put(arg.connector, cInfo);
@@ -569,7 +573,7 @@
                     case NsdManager.REGISTER_SERVICE:
                         cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
-                            cInfo.onRegisterServiceFailed(
+                            cInfo.onRegisterServiceFailedImmediately(
                                     clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
@@ -651,8 +655,8 @@
 
             private void storeLegacyRequestMap(int clientRequestId, int transactionId,
                     ClientInfo clientInfo, int what) {
-                clientInfo.mClientRequests.put(
-                        clientRequestId, new LegacyClientRequest(transactionId, what));
+                clientInfo.mClientRequests.put(clientRequestId, new LegacyClientRequest(
+                        transactionId, what, mClock, mClock.elapsedRealtime()));
                 mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
                 // Remove the cleanup event because here comes a new request.
                 cancelStop();
@@ -660,8 +664,8 @@
 
             private void storeAdvertiserRequestMap(int clientRequestId, int transactionId,
                     ClientInfo clientInfo, @Nullable Network requestedNetwork) {
-                clientInfo.mClientRequests.put(clientRequestId,
-                        new AdvertiserClientRequest(transactionId, requestedNetwork));
+                clientInfo.mClientRequests.put(clientRequestId, new AdvertiserClientRequest(
+                        transactionId, requestedNetwork, mClock, mClock.elapsedRealtime()));
                 mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
                 updateMulticastLock();
             }
@@ -684,8 +688,9 @@
             private void storeDiscoveryManagerRequestMap(int clientRequestId, int transactionId,
                     MdnsListener listener, ClientInfo clientInfo,
                     @Nullable Network requestedNetwork) {
-                clientInfo.mClientRequests.put(clientRequestId,
-                        new DiscoveryManagerRequest(transactionId, listener, requestedNetwork));
+                clientInfo.mClientRequests.put(clientRequestId, new DiscoveryManagerRequest(
+                        transactionId, listener, requestedNetwork, mClock,
+                        mClock.elapsedRealtime()));
                 mTransactionIdToClientInfoMap.put(transactionId, clientInfo);
                 updateMulticastLock();
             }
@@ -838,7 +843,7 @@
                         }
 
                         if (requestLimitReached(clientInfo)) {
-                            clientInfo.onRegisterServiceFailed(
+                            clientInfo.onRegisterServiceFailedImmediately(
                                     clientRequestId, NsdManager.FAILURE_MAX_LIMIT);
                             break;
                         }
@@ -854,8 +859,8 @@
                                 || useAdvertiserForType(registerServiceType)) {
                             if (registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
-                                clientInfo.onRegisterServiceFailed(clientRequestId,
-                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                                clientInfo.onRegisterServiceFailedImmediately(
+                                        clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
                                 break;
                             }
                             serviceInfo.setServiceType(registerServiceType);
@@ -882,7 +887,7 @@
                                 // Return success after mDns reports success
                             } else {
                                 unregisterService(transactionId);
-                                clientInfo.onRegisterServiceFailed(
+                                clientInfo.onRegisterServiceFailedImmediately(
                                         clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
                             }
 
@@ -914,10 +919,12 @@
                         // instead of looking at the flag value.
                         if (request instanceof AdvertiserClientRequest) {
                             mAdvertiser.removeService(transactionId);
-                            clientInfo.onUnregisterServiceSucceeded(clientRequestId);
+                            clientInfo.onUnregisterServiceSucceeded(clientRequestId, transactionId,
+                                    request.calculateRequestDurationMs());
                         } else {
                             if (unregisterService(transactionId)) {
-                                clientInfo.onUnregisterServiceSucceeded(clientRequestId);
+                                clientInfo.onUnregisterServiceSucceeded(clientRequestId,
+                                        transactionId, request.calculateRequestDurationMs());
                             } else {
                                 clientInfo.onUnregisterServiceFailed(
                                         clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -1181,12 +1188,18 @@
                         final RegistrationInfo info = (RegistrationInfo) obj;
                         final String name = info.serviceName;
                         servInfo = new NsdServiceInfo(name, null /* serviceType */);
-                        clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo);
+                        final ClientRequest request =
+                                clientInfo.mClientRequests.get(clientRequestId);
+                        clientInfo.onRegisterServiceSucceeded(clientRequestId, servInfo,
+                                transactionId, request.calculateRequestDurationMs());
                         break;
                     }
                     case IMDnsEventListener.SERVICE_REGISTRATION_FAILED:
-                        clientInfo.onRegisterServiceFailed(
-                                clientRequestId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        final ClientRequest request =
+                                clientInfo.mClientRequests.get(clientRequestId);
+                        clientInfo.onRegisterServiceFailed(clientRequestId,
+                                NsdManager.FAILURE_INTERNAL_ERROR, transactionId,
+                                request.calculateRequestDurationMs());
                         break;
                     case IMDnsEventListener.SERVICE_RESOLVED: {
                         final ResolutionInfo info = (ResolutionInfo) obj;
@@ -1566,6 +1579,7 @@
         handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
         mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
                 new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"));
+        mClock = deps.makeClock();
     }
 
     /**
@@ -1655,6 +1669,21 @@
         public int getCallingUid() {
             return Binder.getCallingUid();
         }
+
+        /**
+         * @see NetworkNsdReportedMetrics
+         */
+        public NetworkNsdReportedMetrics makeNetworkNsdReportedMetrics(
+                boolean isLegacy, int clientId) {
+            return new NetworkNsdReportedMetrics(isLegacy, clientId);
+        }
+
+        /**
+         * @see MdnsUtils.Clock
+         */
+        public Clock makeClock() {
+            return new Clock();
+        }
     }
 
     /**
@@ -1755,7 +1784,9 @@
             // onRegisterServiceSucceeded only has the service name in its info. This aligns with
             // historical behavior.
             final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
-            clientInfo.onRegisterServiceSucceeded(clientRequestId, cbInfo);
+            final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
+            clientInfo.onRegisterServiceSucceeded(
+                    clientRequestId, cbInfo, transactionId, request.calculateRequestDurationMs());
         }
 
         @Override
@@ -1765,8 +1796,9 @@
 
             final int clientRequestId = getClientRequestIdOrLog(clientInfo, transactionId);
             if (clientRequestId < 0) return;
-
-            clientInfo.onRegisterServiceFailed(clientRequestId, errorCode);
+            final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
+            clientInfo.onRegisterServiceFailed(clientRequestId, errorCode, transactionId,
+                    request.calculateRequestDurationMs());
         }
 
         private ClientInfo getClientInfoOrLog(int transactionId) {
@@ -2026,17 +2058,27 @@
 
     private abstract static class ClientRequest {
         private final int mTransactionId;
+        private final Clock mClock;
+        private final long mStartTimeMs;
 
-        private ClientRequest(int transactionId) {
+        private ClientRequest(int transactionId, @NonNull Clock clock, long startTimeMs) {
             mTransactionId = transactionId;
+            mClock = clock;
+            mStartTimeMs = startTimeMs;
+        }
+
+        public long calculateRequestDurationMs() {
+            final long stopTimeMs = mClock.elapsedRealtime();
+            return stopTimeMs - mStartTimeMs;
         }
     }
 
     private static class LegacyClientRequest extends ClientRequest {
         private final int mRequestCode;
 
-        private LegacyClientRequest(int transactionId, int requestCode) {
-            super(transactionId);
+        private LegacyClientRequest(int transactionId, int requestCode, @NonNull Clock clock,
+                long startTimeMs) {
+            super(transactionId, clock, startTimeMs);
             mRequestCode = requestCode;
         }
     }
@@ -2045,8 +2087,9 @@
         @Nullable
         private final Network mRequestedNetwork;
 
-        private JavaBackendClientRequest(int transactionId, @Nullable Network requestedNetwork) {
-            super(transactionId);
+        private JavaBackendClientRequest(int transactionId, @Nullable Network requestedNetwork,
+                @NonNull Clock clock, long startTimeMs) {
+            super(transactionId, clock, startTimeMs);
             mRequestedNetwork = requestedNetwork;
         }
 
@@ -2057,8 +2100,9 @@
     }
 
     private static class AdvertiserClientRequest extends JavaBackendClientRequest {
-        private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork) {
-            super(transactionId, requestedNetwork);
+        private AdvertiserClientRequest(int transactionId, @Nullable Network requestedNetwork,
+                @NonNull Clock clock, long startTimeMs) {
+            super(transactionId, requestedNetwork, clock, startTimeMs);
         }
     }
 
@@ -2067,8 +2111,8 @@
         private final MdnsListener mListener;
 
         private DiscoveryManagerRequest(int transactionId, @NonNull MdnsListener listener,
-                @Nullable Network requestedNetwork) {
-            super(transactionId, requestedNetwork);
+                @Nullable Network requestedNetwork, @NonNull Clock clock, long startTimeMs) {
+            super(transactionId, requestedNetwork, clock, startTimeMs);
             mListener = listener;
         }
     }
@@ -2161,6 +2205,8 @@
 
                 if (request instanceof AdvertiserClientRequest) {
                     mAdvertiser.removeService(transactionId);
+                    mMetrics.reportServiceUnregistration(
+                            transactionId, request.calculateRequestDurationMs());
                     continue;
                 }
 
@@ -2177,6 +2223,8 @@
                         break;
                     case NsdManager.REGISTER_SERVICE:
                         unregisterService(transactionId);
+                        mMetrics.reportServiceUnregistration(
+                                transactionId, request.calculateRequestDurationMs());
                         break;
                     default:
                         break;
@@ -2268,7 +2316,13 @@
             }
         }
 
-        void onRegisterServiceFailed(int listenerKey, int error) {
+        void onRegisterServiceFailedImmediately(int listenerKey, int error) {
+            onRegisterServiceFailed(listenerKey, error, NO_TRANSACTION, 0 /* durationMs */);
+        }
+
+        void onRegisterServiceFailed(int listenerKey, int error, int transactionId,
+                long durationMs) {
+            mMetrics.reportServiceRegistrationFailed(transactionId, durationMs);
             try {
                 mCb.onRegisterServiceFailed(listenerKey, error);
             } catch (RemoteException e) {
@@ -2276,7 +2330,9 @@
             }
         }
 
-        void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+        void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info, int transactionId,
+                long durationMs) {
+            mMetrics.reportServiceRegistrationSucceeded(transactionId, durationMs);
             try {
                 mCb.onRegisterServiceSucceeded(listenerKey, info);
             } catch (RemoteException e) {
@@ -2292,7 +2348,8 @@
             }
         }
 
-        void onUnregisterServiceSucceeded(int listenerKey) {
+        void onUnregisterServiceSucceeded(int listenerKey, int transactionId, long durationMs) {
+            mMetrics.reportServiceUnregistration(transactionId, durationMs);
             try {
                 mCb.onUnregisterServiceSucceeded(listenerKey);
             } catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
index 0eebc61..161669b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity.mdns;
 
+import android.annotation.NonNull;
 import android.util.ArraySet;
 
 import java.util.Set;
@@ -47,5 +48,17 @@
             }
             executor.shutdownNow();
         }
+        serviceTypeClientSchedulerExecutors.clear();
+    }
+
+    /**
+     * Shutdown one executor service and remove the executor service from the set.
+     * @param executorService the executorService to be shutdown
+     */
+    public void shutdownExecutorService(@NonNull ScheduledExecutorService executorService) {
+        if (!executorService.isShutdown()) {
+            executorService.shutdownNow();
+        }
+        serviceTypeClientSchedulerExecutors.remove(executorService);
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index f386dd4..d55098c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -204,6 +204,7 @@
                         if (serviceTypeClient == null) return;
                         // Notify all listeners that all services are removed from this socket.
                         serviceTypeClient.notifySocketDestroyed();
+                        executorProvider.shutdownExecutorService(serviceTypeClient.getExecutor());
                         perSocketServiceTypeClients.remove(serviceTypeClient);
                     }
                 });
@@ -238,6 +239,7 @@
             if (serviceTypeClient.stopSendAndReceive(listener)) {
                 // No listener is registered for the service type anymore, remove it from the list
                 // of the service type clients.
+                executorProvider.shutdownExecutorService(serviceTypeClient.getExecutor());
                 perSocketServiceTypeClients.remove(serviceTypeClient);
             }
         }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 0752c01..b5fd8a0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -39,7 +39,6 @@
 import java.net.Inet6Address;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
@@ -330,6 +329,13 @@
         }
     }
 
+    /**
+     * Get the executor service.
+     */
+    public ScheduledExecutorService getExecutor() {
+        return executor;
+    }
+
     private void removeScheduledTask() {
         dependencies.removeMessages(handler, EVENT_START_QUERYTASK);
         sharedLog.log("Remove EVENT_START_QUERYTASK"
@@ -538,145 +544,6 @@
         return new MdnsPacketWriter(DEFAULT_MTU);
     }
 
-    // A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
-    // Call to getConfigForNextRun returns a config that can be used to build the next query task.
-    @VisibleForTesting
-    static class QueryTaskConfig {
-
-        private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
-                (int) MdnsConfigs.initialTimeBetweenBurstsMs();
-        private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
-        private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
-        private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
-                (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
-        private static final int QUERIES_PER_BURST_PASSIVE_MODE =
-                (int) MdnsConfigs.queriesPerBurstPassive();
-        private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
-        // The following fields are used by QueryTask so we need to test them.
-        @VisibleForTesting
-        final List<String> subtypes;
-        private final boolean alwaysAskForUnicastResponse =
-                MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
-        private final boolean usePassiveMode;
-        private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
-        private final int numOfQueriesBeforeBackoff;
-        @VisibleForTesting
-        final int transactionId;
-        @VisibleForTesting
-        final boolean expectUnicastResponse;
-        private final int queriesPerBurst;
-        private final int timeBetweenBurstsInMs;
-        private final int burstCounter;
-        private final long delayUntilNextTaskWithoutBackoffMs;
-        private final boolean isFirstBurst;
-        private final long queryCount;
-        @NonNull private final SocketKey socketKey;
-
-
-        QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
-                boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
-                int queriesPerBurst, int timeBetweenBurstsInMs,
-                long delayUntilNextTaskWithoutBackoffMs) {
-            this.subtypes = new ArrayList<>(other.subtypes);
-            this.usePassiveMode = other.usePassiveMode;
-            this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
-            this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
-            this.transactionId = transactionId;
-            this.expectUnicastResponse = expectUnicastResponse;
-            this.queriesPerBurst = queriesPerBurst;
-            this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
-            this.burstCounter = burstCounter;
-            this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
-            this.isFirstBurst = isFirstBurst;
-            this.queryCount = queryCount;
-            this.socketKey = other.socketKey;
-        }
-        QueryTaskConfig(@NonNull Collection<String> subtypes,
-                boolean usePassiveMode,
-                boolean onlyUseIpv6OnIpv6OnlyNetworks,
-                int numOfQueriesBeforeBackoff,
-                @Nullable SocketKey socketKey) {
-            this.usePassiveMode = usePassiveMode;
-            this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
-            this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
-            this.subtypes = new ArrayList<>(subtypes);
-            this.queriesPerBurst = QUERIES_PER_BURST;
-            this.burstCounter = 0;
-            this.transactionId = 1;
-            this.expectUnicastResponse = true;
-            this.isFirstBurst = true;
-            // Config the scan frequency based on the scan mode.
-            if (this.usePassiveMode) {
-                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
-                // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
-                // queries.
-                this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
-            } else {
-                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
-                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
-                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
-                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
-                this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
-            }
-            this.socketKey = socketKey;
-            this.queryCount = 0;
-            this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
-        }
-
-        QueryTaskConfig getConfigForNextRun() {
-            long newQueryCount = queryCount + 1;
-            int newTransactionId = transactionId + 1;
-            if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
-                newTransactionId = 1;
-            }
-            boolean newExpectUnicastResponse = false;
-            boolean newIsFirstBurst = isFirstBurst;
-            int newQueriesPerBurst = queriesPerBurst;
-            int newBurstCounter = burstCounter + 1;
-            long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
-            int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
-            // Only the first query expects uni-cast response.
-            if (newBurstCounter == queriesPerBurst) {
-                newBurstCounter = 0;
-
-                if (alwaysAskForUnicastResponse) {
-                    newExpectUnicastResponse = true;
-                }
-                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
-                // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
-                // queries.
-                if (isFirstBurst) {
-                    newIsFirstBurst = false;
-                    if (usePassiveMode) {
-                        newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
-                    }
-                }
-                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
-                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
-                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
-                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
-                newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
-                if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
-                    newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
-                            TIME_BETWEEN_BURSTS_MS);
-                }
-            } else {
-                newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
-            }
-            return new QueryTaskConfig(this, newQueryCount, newTransactionId,
-                    newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
-                    newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
-        }
-
-        private boolean shouldUseQueryBackoff() {
-            // Don't enable backoff mode during the burst or in the first burst
-            if (burstCounter != 0 || isFirstBurst) {
-                return false;
-            }
-            return queryCount > numOfQueriesBeforeBackoff;
-        }
-    }
-
     private List<MdnsResponse> makeResponsesForResolve(@NonNull SocketKey socketKey) {
         final List<MdnsResponse> resolveResponses = new ArrayList<>();
         for (int i = 0; i < listeners.size(); i++) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
new file mode 100644
index 0000000..19282b0
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/QueryTaskConfig.java
@@ -0,0 +1,172 @@
+/*
+ * 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.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
+ * Call to getConfigForNextRun returns a config that can be used to build the next query task.
+ */
+public class QueryTaskConfig {
+
+    private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
+            (int) MdnsConfigs.initialTimeBetweenBurstsMs();
+    private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
+    private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
+    private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
+            (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
+    private static final int QUERIES_PER_BURST_PASSIVE_MODE =
+            (int) MdnsConfigs.queriesPerBurstPassive();
+    private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
+    // The following fields are used by QueryTask so we need to test them.
+    @VisibleForTesting
+    final List<String> subtypes;
+    private final boolean alwaysAskForUnicastResponse =
+            MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
+    private final boolean usePassiveMode;
+    final boolean onlyUseIpv6OnIpv6OnlyNetworks;
+    private final int numOfQueriesBeforeBackoff;
+    @VisibleForTesting
+    final int transactionId;
+    @VisibleForTesting
+    final boolean expectUnicastResponse;
+    private final int queriesPerBurst;
+    private final int timeBetweenBurstsInMs;
+    private final int burstCounter;
+    final long delayUntilNextTaskWithoutBackoffMs;
+    private final boolean isFirstBurst;
+    private final long queryCount;
+    @NonNull
+    final SocketKey socketKey;
+
+    QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
+            boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
+            int queriesPerBurst, int timeBetweenBurstsInMs,
+            long delayUntilNextTaskWithoutBackoffMs) {
+        this.subtypes = new ArrayList<>(other.subtypes);
+        this.usePassiveMode = other.usePassiveMode;
+        this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
+        this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
+        this.transactionId = transactionId;
+        this.expectUnicastResponse = expectUnicastResponse;
+        this.queriesPerBurst = queriesPerBurst;
+        this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
+        this.burstCounter = burstCounter;
+        this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
+        this.isFirstBurst = isFirstBurst;
+        this.queryCount = queryCount;
+        this.socketKey = other.socketKey;
+    }
+    QueryTaskConfig(@NonNull Collection<String> subtypes,
+            boolean usePassiveMode,
+            boolean onlyUseIpv6OnIpv6OnlyNetworks,
+            int numOfQueriesBeforeBackoff,
+            @Nullable SocketKey socketKey) {
+        this.usePassiveMode = usePassiveMode;
+        this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
+        this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
+        this.subtypes = new ArrayList<>(subtypes);
+        this.queriesPerBurst = QUERIES_PER_BURST;
+        this.burstCounter = 0;
+        this.transactionId = 1;
+        this.expectUnicastResponse = true;
+        this.isFirstBurst = true;
+        // Config the scan frequency based on the scan mode.
+        if (this.usePassiveMode) {
+            // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
+            // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+            // queries.
+            this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
+        } else {
+            // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+            // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+            // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+            // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+            this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
+        }
+        this.socketKey = socketKey;
+        this.queryCount = 0;
+        this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+    }
+
+    /**
+     * Get new QueryTaskConfig for next run.
+     */
+    public QueryTaskConfig getConfigForNextRun() {
+        long newQueryCount = queryCount + 1;
+        int newTransactionId = transactionId + 1;
+        if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
+            newTransactionId = 1;
+        }
+        boolean newExpectUnicastResponse = false;
+        boolean newIsFirstBurst = isFirstBurst;
+        int newQueriesPerBurst = queriesPerBurst;
+        int newBurstCounter = burstCounter + 1;
+        long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
+        int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
+        // Only the first query expects uni-cast response.
+        if (newBurstCounter == queriesPerBurst) {
+            newBurstCounter = 0;
+
+            if (alwaysAskForUnicastResponse) {
+                newExpectUnicastResponse = true;
+            }
+            // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
+            // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+            // queries.
+            if (isFirstBurst) {
+                newIsFirstBurst = false;
+                if (usePassiveMode) {
+                    newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
+                }
+            }
+            // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+            // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+            // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+            // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+            newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
+            if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
+                newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
+                        TIME_BETWEEN_BURSTS_MS);
+            }
+        } else {
+            newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+        }
+        return new QueryTaskConfig(this, newQueryCount, newTransactionId,
+                newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
+                newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
+    }
+
+    /**
+     * Determine if the query backoff should be used.
+     */
+    public boolean shouldUseQueryBackoff() {
+        // Don't enable backoff mode during the burst or in the first burst
+        if (burstCounter != 0 || isFirstBurst) {
+            return false;
+        }
+        return queryCount > numOfQueriesBeforeBackoff;
+    }
+}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
new file mode 100644
index 0000000..77383ad
--- /dev/null
+++ b/tests/benchmark/Android.bp
@@ -0,0 +1,42 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "ConnectivityBenchmarkTests",
+    defaults: [
+        "framework-connectivity-internal-test-defaults",
+    ],
+    platform_apis: true,
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "net-tests-utils",
+        "service-connectivity-pre-jarjar",
+        "service-connectivity-tiramisu-pre-jarjar",
+    ],
+    test_suites: ["device-tests"],
+    jarjar_rules: ":connectivity-jarjar-rules",
+}
+
diff --git a/tests/benchmark/AndroidManifest.xml b/tests/benchmark/AndroidManifest.xml
new file mode 100644
index 0000000..bd2fce5
--- /dev/null
+++ b/tests/benchmark/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.server.connectivity.benchmarktests">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.server.connectivity.benchmarktests"
+         android:label="Connectivity Benchmark Tests" />
+</manifest>
diff --git a/tests/benchmark/OWNERS b/tests/benchmark/OWNERS
new file mode 100644
index 0000000..3101da5
--- /dev/null
+++ b/tests/benchmark/OWNERS
@@ -0,0 +1,2 @@
+# Bug template url: http://b/new?component=31808
+# TODO: move bug template config to common owners file once b/226427845 is resolved
\ No newline at end of file
diff --git a/tests/benchmark/res/raw/netstats-many-uids-zip b/tests/benchmark/res/raw/netstats-many-uids-zip
new file mode 100644
index 0000000..22e8254
--- /dev/null
+++ b/tests/benchmark/res/raw/netstats-many-uids-zip
Binary files differ
diff --git a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
new file mode 100644
index 0000000..8492268
--- /dev/null
+++ b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.net.benchmarktests
+
+import android.net.NetworkStats.NonMonotonicObserver
+import android.net.NetworkStatsCollection
+import android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID
+import android.os.DropBoxManager
+import androidx.test.InstrumentationRegistry
+import com.android.internal.util.FileRotator
+import com.android.internal.util.FileRotator.Reader
+import com.android.server.connectivity.benchmarktests.R
+import com.android.server.net.NetworkStatsRecorder
+import java.io.BufferedInputStream
+import java.io.DataInputStream
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.nio.file.Files
+import java.util.concurrent.TimeUnit
+import java.util.zip.ZipInputStream
+import kotlin.test.assertTrue
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+
+@RunWith(JUnit4::class)
+class NetworkStatsTest {
+    companion object {
+        private val DEFAULT_BUFFER_SIZE = 8192
+        private val UID_COLLECTION_BUCKET_DURATION_MS = TimeUnit.HOURS.toMillis(2)
+        private val UID_RECORDER_ROTATE_AGE_MS = TimeUnit.DAYS.toMillis(15)
+        private val UID_RECORDER_DELETE_AGE_MS = TimeUnit.DAYS.toMillis(90)
+
+        private val testFilesDir by lazy {
+            // These file generated by using real user dataset which has many uid records
+            // and agreed to share the dataset for testing purpose. These dataset can be
+            // extracted from rooted devices by using
+            // "adb pull /data/misc/apexdata/com.android.tethering/netstats" command.
+            val zipInputStream =
+                ZipInputStream(getInputStreamForResource(R.raw.netstats_many_uids_zip))
+            unzipToTempDir(zipInputStream)
+        }
+
+        private val uidTestFiles: List<File> by lazy {
+            getSortedListForPrefix(testFilesDir, "uid")
+        }
+
+        // Test results shows the test cases who read the file first will take longer time to
+        // execute, and reading time getting shorter each time. Read files several times prior to
+        // tests to minimize the impact. This cannot live in setUp() since the time
+        // spent on the file reading will be attributed to the time spent on the individual
+        // test case.
+        @JvmStatic
+        @BeforeClass
+        fun setUpOnce() {
+            for (i in 1..10) {
+                val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
+                for (file in uidTestFiles) {
+                    readFile(file, collection)
+                }
+            }
+        }
+
+        private fun getInputStreamForResource(resourceId: Int): DataInputStream {
+            return DataInputStream(
+                InstrumentationRegistry.getContext()
+                    .getResources().openRawResource(resourceId)
+            )
+        }
+
+        private fun unzipToTempDir(zis: ZipInputStream): File {
+            val statsDir =
+                Files.createTempDirectory(NetworkStatsTest::class.simpleName).toFile()
+            while (true) {
+                val entryName = zis.nextEntry?.name ?: break
+                val file = File(statsDir, entryName)
+                FileOutputStream(file).use { zis.copyTo(it, DEFAULT_BUFFER_SIZE) }
+            }
+            return statsDir
+        }
+
+        // List [xt|uid|uid_tag].<start>-<end> files under the given directory.
+        private fun getSortedListForPrefix(statsDir: File, prefix: String): List<File> {
+            assertTrue(statsDir.exists())
+            return (statsDir.list() ?: arrayOf()).mapNotNull {
+                if (it.startsWith("$prefix.")) File(statsDir, it) else null
+            }.sorted()
+        }
+
+        private fun readFile(file: File, reader: Reader) =
+            BufferedInputStream(FileInputStream(file)).use {
+                reader.read(it)
+            }
+    }
+
+    @Test
+    fun testReadCollection_manyUids() {
+        for (i in 1..10) {
+            val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
+            for (file in uidTestFiles) {
+                readFile(file, collection)
+            }
+        }
+    }
+
+    @Test
+    fun testReadFromRecorder_manyUids() {
+        for (i in 1..10) {
+            val recorder = NetworkStatsRecorder(
+                FileRotator(
+                    testFilesDir, PREFIX_UID, UID_RECORDER_ROTATE_AGE_MS, UID_RECORDER_DELETE_AGE_MS
+                ),
+                mock<NonMonotonicObserver<String>>(),
+                mock(DropBoxManager::class.java),
+                PREFIX_UID,
+                UID_COLLECTION_BUCKET_DURATION_MS,
+                false /* includeTags */,
+                false /* wipeOnError */
+            )
+            recorder.orLoadCompleteLocked
+        }
+    }
+
+    inline fun <reified T : Any> mock(): T = mock(T::class.java)
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 891c2dd..e55ba63 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -32,6 +32,7 @@
     // Only compile source java files in this apk.
     srcs: ["src/**/*.java"],
     libs: [
+        "net-tests-utils-host-device-common",
         "cts-tradefed",
         "tradefed",
     ],
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index 2cfd7af..dc86fb1 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -11,6 +11,20 @@
         },
         {
           "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+        },
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      // Postsubmit on virtual devices to monitor flakiness of @SkipPresubmit methods
+      "name": "CtsHostsideNetworkTests",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
         }
       ]
     }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 691ac90..4f21af7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,6 +18,8 @@
 
 import android.platform.test.annotations.RequiresDevice;
 
+import com.android.testutils.SkipPresubmit;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -35,11 +37,13 @@
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testChangeUnderlyingNetworks() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testDefault() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -162,6 +166,7 @@
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testSetVpnDefaultForUids() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index dc22369..1d79806 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -49,6 +49,7 @@
 import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
 import com.android.testutils.DeviceConfigRule
 import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.SkipMainlinePresubmit
 import com.android.testutils.TestHttpServer
 import com.android.testutils.TestHttpServer.Request
 import com.android.testutils.TestableNetworkCallback
@@ -137,6 +138,7 @@
     }
 
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     fun testCaptivePortalIsNotDefaultNetwork() {
         assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
         assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a0508e1..e978664 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -197,6 +197,8 @@
 import com.android.testutils.DeviceInfoUtils;
 import com.android.testutils.DumpTestUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipMainlinePresubmit;
+import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TestHttpServer;
 import com.android.testutils.TestNetworkTracker;
 import com.android.testutils.TestableNetworkCallback;
@@ -1017,6 +1019,7 @@
 
     @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testIsPrivateDnsBroken() throws InterruptedException {
         final String invalidPrivateDnsServer = "invalidhostname.example.com";
         final String goodPrivateDnsServer = "dns.google";
@@ -1124,6 +1127,7 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRegisterNetworkCallback_withPendingIntent() {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
@@ -1267,6 +1271,7 @@
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
         runIdenticalPendingIntentsRequestTest(false /* useListen */);
     }
@@ -2129,6 +2134,7 @@
      */
     @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
     @Test
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     public void testSetAirplaneMode() throws Exception{
         // 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
@@ -2560,10 +2566,9 @@
         assertThrows(SecurityException.class, () -> mCm.factoryReset());
     }
 
-    // @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
-    // @Test
-    // Temporarily disable the unreliable test, which is blocked by b/254183718.
-    private void testFactoryReset() throws Exception {
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    @Test
+    public void testFactoryReset() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
 
         // Store current settings.
@@ -2592,6 +2597,7 @@
             // prevent the race condition between airplane mode enabled and the followed
             // up wifi tethering enabled.
             tetherEventCallback.expectNoTetheringActive();
+            tetherUtils.expectSoftApDisabled();
 
             // start wifi tethering
             tetherUtils.startWifiTethering(tetherEventCallback);
@@ -2738,6 +2744,7 @@
      */
     @AppModeFull(reason = "Instant apps cannot create test networks")
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testSetOemNetworkPreferenceForTestOnlyPref() throws Exception {
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
         // shims, and @IgnoreUpTo does not check that.
@@ -2892,6 +2899,7 @@
 
     @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
         assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
@@ -3197,6 +3205,7 @@
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     public void testMobileDataPreferredUids() throws Exception {
         assumeTrue(TestUtils.shouldTestSApis());
         final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 3821cea..308aead 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -59,6 +59,7 @@
 import com.android.net.module.util.DnsPacket;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DnsResolverModuleTest;
 import com.android.testutils.SkipPresubmit;
 
 import org.junit.After;
@@ -317,51 +318,61 @@
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQuery() throws Exception {
         doTestRawQuery(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryInline() throws Exception {
         doTestRawQuery(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryBlob() throws Exception {
         doTestRawQueryBlob(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryBlobInline() throws Exception {
         doTestRawQueryBlob(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryRoot() throws Exception {
         doTestRawQueryRoot(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryRootInline() throws Exception {
         doTestRawQueryRoot(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryNXDomain() throws Exception {
         doTestRawQueryNXDomain(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryNXDomainInline() throws Exception {
         doTestRawQueryNXDomain(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryNXDomainWithPrivateDns() throws Exception {
         doTestRawQueryNXDomainWithPrivateDns(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
         doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
     }
@@ -610,41 +621,49 @@
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddress() throws Exception {
         doTestQueryForInetAddress(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddressInline() throws Exception {
         doTestQueryForInetAddress(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddressIpv4() throws Exception {
         doTestQueryForInetAddressIpv4(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddressIpv4Inline() throws Exception {
         doTestQueryForInetAddressIpv4(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddressIpv6() throws Exception {
         doTestQueryForInetAddressIpv6(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testQueryForInetAddressIpv6Inline() throws Exception {
         doTestQueryForInetAddressIpv6(mExecutorInline);
     }
 
     @Test
+    @DnsResolverModuleTest
     public void testContinuousQueries() throws Exception {
         doTestContinuousQueries(mExecutor);
     }
 
     @Test
+    @DnsResolverModuleTest
     @SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
     public void testContinuousQueriesInline() throws Exception {
         doTestContinuousQueries(mExecutorInline);
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 805dd65..81834a9 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -71,6 +71,8 @@
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipMainlinePresubmit;
+import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TestableNetworkCallback;
 
 import org.bouncycastle.x509.X509V1CertificateGenerator;
@@ -640,6 +642,7 @@
     }
 
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testStartStopVpnProfileV4() throws Exception {
         doTestStartStopVpnProfile(false /* testIpv6Only */, false /* requiresValidation */,
                 false /* testSessionKey */, false /* testIkeTunConnParams */);
@@ -653,12 +656,14 @@
     }
 
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testStartStopVpnProfileV6() throws Exception {
         doTestStartStopVpnProfile(true /* testIpv6Only */, false /* requiresValidation */,
                 false /* testSessionKey */, false /* testIkeTunConnParams */);
     }
 
     @Test @IgnoreUpTo(SC_V2)
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     public void testStartStopVpnProfileV6WithValidation() throws Exception {
         assumeTrue(TestUtils.shouldTestTApis());
         doTestStartStopVpnProfile(true /* testIpv6Only */, true /* requiresValidation */,
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index cc0a5df..fe86a90 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -81,6 +81,7 @@
 import com.android.modules.utils.build.SdkLevel;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.SkipMainlinePresubmit;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -716,6 +717,7 @@
     }
 
     @Test
+    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testIkeOverUdpEncapSocket() throws Exception {
         // IPv6 not supported for UDP-encap-ESP
         InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
new file mode 100644
index 0000000..961c422
--- /dev/null
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.metrics
+
+import android.os.Build
+import android.stats.connectivity.MdnsQueryResult
+import android.stats.connectivity.NsdEventType
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class NetworkNsdReportedMetricsTest {
+    private val deps = mock(NetworkNsdReportedMetrics.Dependencies::class.java)
+
+    @Test
+    fun testReportServiceRegistrationSucceeded() {
+        val clientId = 99
+        val transactionId = 100
+        val durationMs = 10L
+        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+        metrics.reportServiceRegistrationSucceeded(transactionId, durationMs)
+
+        val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+        verify(deps).statsWrite(eventCaptor.capture())
+        eventCaptor.value.let {
+            assertTrue(it.isLegacy)
+            assertEquals(clientId, it.clientId)
+            assertEquals(transactionId, it.transactionId)
+            assertEquals(NsdEventType.NET_REGISTER, it.type)
+            assertEquals(MdnsQueryResult.MQR_SERVICE_REGISTERED, it.queryResult)
+            assertEquals(durationMs, it.eventDurationMillisec)
+        }
+    }
+
+    @Test
+    fun testReportServiceRegistrationFailed() {
+        val clientId = 99
+        val transactionId = 100
+        val durationMs = 10L
+        val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+        metrics.reportServiceRegistrationFailed(transactionId, durationMs)
+
+        val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+        verify(deps).statsWrite(eventCaptor.capture())
+        eventCaptor.value.let {
+            assertFalse(it.isLegacy)
+            assertEquals(clientId, it.clientId)
+            assertEquals(transactionId, it.transactionId)
+            assertEquals(NsdEventType.NET_REGISTER, it.type)
+            assertEquals(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED, it.queryResult)
+            assertEquals(durationMs, it.eventDurationMillisec)
+        }
+    }
+
+    @Test
+    fun testReportServiceUnregistration() {
+        val clientId = 99
+        val transactionId = 100
+        val durationMs = 10L
+        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+        metrics.reportServiceUnregistration(transactionId, durationMs)
+
+        val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+        verify(deps).statsWrite(eventCaptor.capture())
+        eventCaptor.value.let {
+            assertTrue(it.isLegacy)
+            assertEquals(clientId, it.clientId)
+            assertEquals(transactionId, it.transactionId)
+            assertEquals(NsdEventType.NET_REGISTER, it.type)
+            assertEquals(MdnsQueryResult.MQR_SERVICE_UNREGISTERED, it.queryResult)
+            assertEquals(durationMs, it.eventDurationMillisec)
+        }
+    }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index bbde9b4..5dd4048 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -422,6 +422,7 @@
 import com.android.testutils.FunctionalUtils.ThrowingRunnable;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
+import com.android.testutils.SkipPresubmit;
 import com.android.testutils.TestableNetworkCallback;
 import com.android.testutils.TestableNetworkOfferCallback;
 
@@ -7414,6 +7415,7 @@
         assertPinnedToWifiWithCellDefault();
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testNetworkCallbackMaximum() throws Exception {
         final int MAX_REQUESTS = 100;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index f51b28d..55384b3 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -43,7 +43,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
@@ -95,6 +97,7 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.metrics.NetworkNsdReportedMetrics;
 import com.android.server.NsdService.Dependencies;
 import com.android.server.connectivity.mdns.MdnsAdvertiser;
 import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
@@ -104,6 +107,7 @@
 import com.android.server.connectivity.mdns.MdnsServiceInfo;
 import com.android.server.connectivity.mdns.MdnsSocketProvider;
 import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketRequestMonitor;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
@@ -138,6 +142,7 @@
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
     private static final long CLEANUP_DELAY_MS = 500;
     private static final long TIMEOUT_MS = 500;
+    private static final long TEST_TIME_MS = 123L;
     private static final String SERVICE_NAME = "a_name";
     private static final String SERVICE_TYPE = "_test._tcp";
     private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
@@ -164,6 +169,8 @@
     @Mock WifiManager mWifiManager;
     @Mock WifiManager.MulticastLock mMulticastLock;
     @Mock ActivityManager mActivityManager;
+    @Mock NetworkNsdReportedMetrics mMetrics;
+    @Mock MdnsUtils.Clock mClock;
     SocketRequestMonitor mSocketRequestMonitor;
     OnUidImportanceListener mUidImportanceListener;
     HandlerThread mThread;
@@ -210,6 +217,9 @@
         doReturn(DEFAULT_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF).when(mDeps).getDeviceConfigInt(
                 eq(NsdService.MDNS_CONFIG_RUNNING_APP_ACTIVE_IMPORTANCE_CUTOFF), anyInt());
         doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
+        doReturn(mMetrics).when(mDeps).makeNetworkNsdReportedMetrics(anyBoolean(), anyInt());
+        doReturn(mClock).when(mDeps).makeClock();
+        doReturn(TEST_TIME_MS).when(mClock).elapsedRealtime();
         mService = makeService();
         final ArgumentCaptor<SocketRequestMonitor> cbMonitorCaptor =
                 ArgumentCaptor.forClass(SocketRequestMonitor.class);
@@ -512,14 +522,16 @@
                 eq(SERVICE_NAME), eq(SERVICE_TYPE), eq(PORT), any(), eq(IFACE_IDX_ANY));
 
         // Register service successfully.
+        final int regId = regIdCaptor.getValue();
         final RegistrationInfo registrationInfo = new RegistrationInfo(
-                regIdCaptor.getValue(),
+                regId,
                 IMDnsEventListener.SERVICE_REGISTERED,
                 SERVICE_NAME,
                 SERVICE_TYPE,
                 PORT,
                 new byte[0] /* txtRecord */,
                 IFACE_IDX_ANY);
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
         eventListener.onServiceRegistrationStatus(registrationInfo);
 
         final ArgumentCaptor<NsdServiceInfo> registeredInfoCaptor =
@@ -528,19 +540,22 @@
                 .onServiceRegistered(registeredInfoCaptor.capture());
         final NsdServiceInfo registeredInfo = registeredInfoCaptor.getValue();
         assertEquals(SERVICE_NAME, registeredInfo.getServiceName());
+        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
 
         // Fail to register service.
         final RegistrationInfo registrationFailedInfo = new RegistrationInfo(
-                regIdCaptor.getValue(),
+                regId,
                 IMDnsEventListener.SERVICE_REGISTRATION_FAILED,
                 null /* serviceName */,
                 null /* registrationType */,
                 0 /* port */,
                 new byte[0] /* txtRecord */,
                 IFACE_IDX_ANY);
+        doReturn(TEST_TIME_MS + 20L).when(mClock).elapsedRealtime();
         eventListener.onServiceRegistrationStatus(registrationFailedInfo);
         verify(regListener, timeout(TIMEOUT_MS))
                 .onRegistrationFailed(any(), eq(FAILURE_INTERNAL_ERROR));
+        verify(mMetrics).reportServiceRegistrationFailed(regId, 20L /* durationMs */);
     }
 
     @Test
@@ -1215,17 +1230,22 @@
 
         // Verify onServiceRegistered callback
         final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
-        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+        final int regId = idCaptor.getValue();
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
+        cb.onRegisterServiceSucceeded(regId, regInfo);
 
         verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(argThat(info -> matches(info,
                 new NsdServiceInfo(regInfo.getServiceName(), null))));
+        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
 
+        doReturn(TEST_TIME_MS + 100L).when(mClock).elapsedRealtime();
         client.unregisterService(regListener);
         waitForIdle();
         verify(mAdvertiser).removeService(idCaptor.getValue());
         verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
                 argThat(info -> matches(info, regInfo)));
         verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
+        verify(mMetrics).reportServiceUnregistration(regId, 100L /* durationMs */);
     }
 
     @Test
@@ -1251,6 +1271,7 @@
 
         verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
                 argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
+        verify(mMetrics).reportServiceRegistrationFailed(anyInt(), anyLong());
     }
 
     @Test
@@ -1280,10 +1301,13 @@
 
         // Verify onServiceRegistered callback
         final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
-        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+        final int regId = idCaptor.getValue();
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
+        cb.onRegisterServiceSucceeded(regId, regInfo);
 
         verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
                 argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+        verify(mMetrics).reportServiceRegistrationSucceeded(regId, 10L /* durationMs */);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 967083e..b319c30 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -50,6 +50,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -60,6 +61,7 @@
 import android.telephony.TelephonyManager;
 import android.testing.PollingCheck;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -428,6 +430,22 @@
 
         // UiDevice.getLauncherPackageName() requires the test manifest to have a <queries> tag for
         // the launcher intent.
+        // Attempted workaround for b/286550950 where Settings is reported as the launcher
+        PollingCheck.check(
+                "Launcher package name was still settings after " + TEST_TIMEOUT_MS + "ms",
+                TEST_TIMEOUT_MS,
+                () -> {
+                    if ("com.android.settings".equals(uiDevice.getLauncherPackageName())) {
+                        final Intent intent = new Intent(Intent.ACTION_MAIN);
+                        intent.addCategory(Intent.CATEGORY_HOME);
+                        final List<ResolveInfo> acts = ctx.getPackageManager()
+                                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+                        Log.e(NetworkNotificationManagerTest.class.getSimpleName(),
+                                "Got settings as launcher name; launcher activities: " + acts);
+                        return false;
+                    }
+                    return true;
+                });
         final String launcherPackageName = uiDevice.getLauncherPackageName();
         assertTrue(String.format("Launcher (%s) is not shown", launcherPackageName),
                 uiDevice.wait(Until.hasObject(By.pkg(launcherPackageName)),
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 9ae727d..2726267 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -182,6 +182,7 @@
 import com.android.server.VpnTestBase;
 import com.android.server.vcn.util.PersistableBundleUtils;
 import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.SkipPresubmit;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -1713,6 +1714,7 @@
                 errorCode);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
         final IkeProtocolException exception = mock(IkeProtocolException.class);
@@ -1722,6 +1724,7 @@
                 VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
         final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
@@ -1733,6 +1736,7 @@
                 errorCode);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
         final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
@@ -1754,6 +1758,7 @@
                 VpnManager.ERROR_CODE_NETWORK_LOST);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testStartPlatformVpnFailedWithIOException() throws Exception {
         final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
@@ -2386,6 +2391,7 @@
                 true /* areLongLivedTcpConnectionsExpensive */);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testPreferredIpProtocolFromCarrierConfig_v4UDP() throws Exception {
         doTestReadCarrierConfig(createTestCellNc(),
@@ -2398,6 +2404,7 @@
                 false /* areLongLivedTcpConnectionsExpensive */);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testPreferredIpProtocolFromCarrierConfig_v6ESP() throws Exception {
         doTestReadCarrierConfig(createTestCellNc(),
@@ -2410,6 +2417,7 @@
                 false /* areLongLivedTcpConnectionsExpensive */);
     }
 
+    @SkipPresubmit(reason = "Out of SLO flakiness")
     @Test
     public void testPreferredIpProtocolFromCarrierConfig_v6UDP() throws Exception {
         doTestReadCarrierConfig(createTestCellNc(),
@@ -2467,7 +2475,8 @@
         if (expectedReadFromCarrierConfig) {
             final ArgumentCaptor<NetworkCapabilities> ncCaptor =
                     ArgumentCaptor.forClass(NetworkCapabilities.class);
-            verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+            verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+                    .doSendNetworkCapabilities(ncCaptor.capture());
 
             final VpnTransportInfo info =
                     (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 1a4ae5d..e869b91 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -53,6 +53,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
 
 /** Tests for {@link MdnsDiscoveryManager}. */
 @RunWith(DevSdkIgnoreRunner.class)
@@ -80,6 +81,7 @@
     private static final Pair<String, SocketKey> PER_SOCKET_SERVICE_TYPE_2_NETWORK_2 =
             Pair.create(SERVICE_TYPE_2, SOCKET_KEY_NETWORK_2);
     @Mock private ExecutorProvider executorProvider;
+    @Mock private ScheduledExecutorService mockExecutorService;
     @Mock private MdnsSocketClientBase socketClient;
     @Mock private MdnsServiceTypeClient mockServiceTypeClientType1NullNetwork;
     @Mock private MdnsServiceTypeClient mockServiceTypeClientType1Network1;
@@ -128,6 +130,7 @@
                         return null;
                     }
                 };
+        doReturn(mockExecutorService).when(mockServiceTypeClientType1NullNetwork).getExecutor();
     }
 
     @After
@@ -165,11 +168,25 @@
         when(mockServiceTypeClientType1NullNetwork.stopSendAndReceive(mockListenerOne))
                 .thenReturn(true);
         runOnHandler(() -> discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne));
+        verify(executorProvider).shutdownExecutorService(mockExecutorService);
         verify(mockServiceTypeClientType1NullNetwork).stopSendAndReceive(mockListenerOne);
         verify(socketClient).stopDiscovery();
     }
 
     @Test
+    public void onSocketDestroy_shutdownExecutorService() throws IOException {
+        final MdnsSearchOptions options =
+                MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
+        final SocketCreationCallback callback = expectSocketCreationCallback(
+                SERVICE_TYPE_1, mockListenerOne, options);
+        runOnHandler(() -> callback.onSocketCreated(SOCKET_KEY_NULL_NETWORK));
+        verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
+
+        runOnHandler(() -> callback.onSocketDestroyed(SOCKET_KEY_NULL_NETWORK));
+        verify(executorProvider).shutdownExecutorService(mockExecutorService);
+    }
+
+    @Test
     public void registerMultipleListeners() throws IOException {
         final MdnsSearchOptions options =
                 MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 92a2115..1fdfe09 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -52,7 +52,6 @@
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
-import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;