Merge changes I54d367fd,Ib789dc59

* changes:
  cronet: update tests to use android.net.http API
  cronet: use defaults to enable or disable tests
diff --git a/Cronet/TEST_MAPPING b/Cronet/TEST_MAPPING
index b1f3088..9bc5c00 100644
--- a/Cronet/TEST_MAPPING
+++ b/Cronet/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "CtsCronetTestCases"
+      "name": "CtsNetHttpTestCases"
     }
   ]
 }
diff --git a/Cronet/tests/cts/Android.bp b/Cronet/tests/cts/Android.bp
index 95fc9ab..68e3cf1 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -42,7 +42,7 @@
 }
 
 android_test {
-    name: "CtsCronetTestCases",
+    name: "CtsNetHttpTestCases",
     compile_multilib: "both", // Include both the 32 and 64 bit versions
     defaults: [
         "CronetTestJavaDefaults",
diff --git a/Cronet/tests/cts/AndroidTest.xml b/Cronet/tests/cts/AndroidTest.xml
index d2422f1..e0421fd 100644
--- a/Cronet/tests/cts/AndroidTest.xml
+++ b/Cronet/tests/cts/AndroidTest.xml
@@ -23,14 +23,14 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsCronetTestCases.apk" />
+        <option name="test-file-name" value="CtsNetHttpTestCases.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.net.http.cts" />
         <option name="runtime-hint" value="10s" />
     </test>
 
-    <!-- Only run CtsCronetTestcasess in MTS if the Tethering Mainline module is installed. -->
+    <!-- Only run CtsNetHttpTestCases in MTS if the Tethering Mainline module is installed. -->
     <object type="module_controller"
             class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.tethering" />
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 63702f2..f90b3a4 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -1802,7 +1802,7 @@
             TestNetworkAgent mobile, TestNetworkAgent wifi, TestNetworkAgent dun) throws Exception {
         final NetworkCallback dunNetworkCallback = setupDunUpstreamTest(automatic, inOrder);
 
-        // Pretend cellular connected and expect the upstream to be set.
+        // Pretend cellular connected and expect the upstream to be not set.
         mobile.fakeConnect();
         mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
         mLooper.dispatchAll();
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index eb77288..ed841b8 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -195,12 +195,14 @@
     method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
     method public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener);
     method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
+    method public void stopServiceResolution(@NonNull android.net.nsd.NsdManager.ResolveListener);
     method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
     field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
     field public static final String EXTRA_NSD_STATE = "nsd_state";
     field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
     field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
     field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
+    field public static final int FAILURE_OPERATION_NOT_RUNNING = 5; // 0x5
     field public static final int NSD_STATE_DISABLED = 1; // 0x1
     field public static final int NSD_STATE_ENABLED = 2; // 0x2
     field public static final int PROTOCOL_DNS_SD = 1; // 0x1
@@ -224,7 +226,9 @@
 
   public static interface NsdManager.ResolveListener {
     method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
+    method public default void onResolveStopped(@NonNull android.net.nsd.NsdServiceInfo);
     method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
+    method public default void onStopResolutionFailed(@NonNull android.net.nsd.NsdServiceInfo, int);
   }
 
   public final class NsdServiceInfo implements android.os.Parcelable {
diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
index 1a262ec..669efc9 100644
--- a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
+++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
@@ -36,4 +36,6 @@
     void onUnregisterServiceSucceeded(int listenerKey);
     void onResolveServiceFailed(int listenerKey, int error);
     void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+    void onStopResolutionFailed(int listenerKey, int error);
+    void onStopResolutionSucceeded(int listenerKey);
 }
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index b06ae55..a28fd7d 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -32,4 +32,5 @@
     void stopDiscovery(int listenerKey);
     void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
     void startDaemon();
+    void stopResolution(int listenerKey);
 }
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 45def36..1a5a667 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,7 @@
 
 package android.net.nsd;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -44,6 +45,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -230,7 +233,6 @@
 
     /** @hide */
     public static final int DAEMON_CLEANUP                          = 18;
-
     /** @hide */
     public static final int DAEMON_STARTUP                          = 19;
 
@@ -245,6 +247,13 @@
     /** @hide */
     public static final int MDNS_DISCOVERY_MANAGER_EVENT            = 23;
 
+    /** @hide */
+    public static final int STOP_RESOLUTION                         = 24;
+    /** @hide */
+    public static final int STOP_RESOLUTION_FAILED                  = 25;
+    /** @hide */
+    public static final int STOP_RESOLUTION_SUCCEEDED               = 26;
+
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
 
@@ -270,6 +279,9 @@
         EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
         EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
         EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
+        EVENT_NAMES.put(STOP_RESOLUTION, "STOP_RESOLUTION");
+        EVENT_NAMES.put(STOP_RESOLUTION_FAILED, "STOP_RESOLUTION_FAILED");
+        EVENT_NAMES.put(STOP_RESOLUTION_SUCCEEDED, "STOP_RESOLUTION_SUCCEEDED");
     }
 
     /** @hide */
@@ -595,6 +607,16 @@
         public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
             sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info);
         }
+
+        @Override
+        public void onStopResolutionFailed(int listenerKey, int error) {
+            sendError(STOP_RESOLUTION_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onStopResolutionSucceeded(int listenerKey) {
+            sendNoArg(STOP_RESOLUTION_SUCCEEDED, listenerKey);
+        }
     }
 
     /**
@@ -618,6 +640,20 @@
      */
     public static final int FAILURE_MAX_LIMIT                   = 4;
 
+    /**
+     * Indicates that the stop operation failed because it is not running.
+     * This failure is passed with {@link ResolveListener#onStopResolutionFailed}.
+     */
+    public static final int FAILURE_OPERATION_NOT_RUNNING       = 5;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            FAILURE_OPERATION_NOT_RUNNING,
+    })
+    public @interface StopOperationFailureCode {
+    }
+
     /** Interface for callback invocation for service discovery */
     public interface DiscoveryListener {
 
@@ -646,12 +682,49 @@
         public void onServiceUnregistered(NsdServiceInfo serviceInfo);
     }
 
-    /** Interface for callback invocation for service resolution */
+    /**
+     * Callback for use with {@link NsdManager#resolveService} to resolve the service info and use
+     * with {@link NsdManager#stopServiceResolution} to stop resolution.
+     */
     public interface ResolveListener {
 
-        public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolution was failed with an error.
+         *
+         * A resolution operation would call either onServiceResolved or onResolveFailed once based
+         * on the result.
+         */
+        void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
 
-        public void onServiceResolved(NsdServiceInfo serviceInfo);
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolved service info.
+         *
+         * A resolution operation would call either onServiceResolved or onResolveFailed once based
+         * on the result.
+         */
+        void onServiceResolved(NsdServiceInfo serviceInfo);
+
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolution was stopped.
+         *
+         * A stop resolution operation would call either onResolveStopped or onStopResolutionFailed
+         * once based on the result.
+         */
+        default void onResolveStopped(@NonNull NsdServiceInfo serviceInfo) { }
+
+        /**
+         * Called once on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report that stopping resolution failed with an
+         * error.
+         *
+         * A stop resolution operation would call either onResolveStopped or onStopResolutionFailed
+         * once based on the result.
+         */
+        default void onStopResolutionFailed(@NonNull NsdServiceInfo serviceInfo,
+                @StopOperationFailureCode int errorCode) { }
     }
 
     @VisibleForTesting
@@ -744,6 +817,16 @@
                     executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
                             (NsdServiceInfo) obj));
                     break;
+                case STOP_RESOLUTION_FAILED:
+                    removeListener(key);
+                    executor.execute(() -> ((ResolveListener) listener).onStopResolutionFailed(
+                            ns, errorCode));
+                    break;
+                case STOP_RESOLUTION_SUCCEEDED:
+                    removeListener(key);
+                    executor.execute(() -> ((ResolveListener) listener).onResolveStopped(
+                            ns));
+                    break;
                 default:
                     Log.d(TAG, "Ignored " + message);
                     break;
@@ -1079,6 +1162,29 @@
         }
     }
 
+    /**
+     * Stop service resolution initiated with {@link #resolveService}.
+     *
+     * A successful stop is notified with a call to {@link ResolveListener#onResolveStopped}.
+     *
+     * <p> Upon failure to stop service resolution for example if resolution is done or the
+     * requester stops resolution repeatedly, the application is notified
+     * {@link ResolveListener#onStopResolutionFailed} with {@link #FAILURE_OPERATION_NOT_RUNNING}
+     *
+     * @param listener This should be a listener object that was passed to {@link #resolveService}.
+     *                 It identifies the resolution that should be stopped and notifies of a
+     *                 successful or unsuccessful stop. Throws {@code IllegalArgumentException} if
+     *                 the listener was not passed to resolveService before.
+     */
+    public void stopServiceResolution(@NonNull ResolveListener listener) {
+        int id = getListenerKey(listener);
+        try {
+            mService.stopResolution(id);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private static void checkListener(Object listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
     }
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 84b9f12..ce105ce 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -60,6 +60,7 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.PermissionUtils;
 import com.android.server.connectivity.mdns.ExecutorProvider;
+import com.android.server.connectivity.mdns.MdnsAdvertiser;
 import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
 import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
 import com.android.server.connectivity.mdns.MdnsSearchOptions;
@@ -74,6 +75,11 @@
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -89,10 +95,22 @@
 public class NsdService extends INsdManager.Stub {
     private static final String TAG = "NsdService";
     private static final String MDNS_TAG = "mDnsConnector";
+    /**
+     * Enable discovery using the Java DiscoveryManager, instead of the legacy mdnsresponder
+     * implementation.
+     */
     private static final String MDNS_DISCOVERY_MANAGER_VERSION = "mdns_discovery_manager_version";
     private static final String LOCAL_DOMAIN_NAME = "local";
+    // Max label length as per RFC 1034/1035
+    private static final int MAX_LABEL_LENGTH = 63;
 
-    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    /**
+     * Enable advertising using the Java MdnsAdvertiser, instead of the legacy mdnsresponder
+     * implementation.
+     */
+    private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version";
+
+    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;
 
@@ -106,6 +124,8 @@
     private final MdnsDiscoveryManager mMdnsDiscoveryManager;
     @Nullable
     private final MdnsSocketProvider mMdnsSocketProvider;
+    @Nullable
+    private final MdnsAdvertiser mAdvertiser;
     // 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
     // synchronization.
@@ -345,7 +365,7 @@
                                 mLegacyClientCount -= 1;
                             }
                         }
-                        if (mMdnsDiscoveryManager != null) {
+                        if (mMdnsDiscoveryManager != null || mAdvertiser != null) {
                             maybeStopMonitoringSocketsIfNoActiveRequest();
                         }
                         maybeScheduleStop();
@@ -385,6 +405,13 @@
                                     clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
+                    case NsdManager.STOP_RESOLUTION:
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onStopResolutionFailed(
+                                    clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                        }
+                        break;
                     case NsdManager.DAEMON_CLEANUP:
                         maybeStopDaemon();
                         break;
@@ -446,6 +473,7 @@
                 clientInfo.mClientRequests.delete(clientId);
                 mIdToClientInfoMap.remove(globalId);
                 maybeScheduleStop();
+                maybeStopMonitoringSocketsIfNoActiveRequest();
             }
 
             private void storeListenerMap(int clientId, int transactionId, MdnsListener listener,
@@ -484,8 +512,31 @@
                 final Matcher matcher = serviceTypePattern.matcher(serviceType);
                 if (!matcher.matches()) return null;
                 return matcher.group(1) == null
-                        ? serviceType + ".local"
-                        : matcher.group(1) + "_sub." + matcher.group(2) + ".local";
+                        ? serviceType
+                        : matcher.group(1) + "_sub." + matcher.group(2);
+            }
+
+            /**
+             * Truncate a service name to up to 63 UTF-8 bytes.
+             *
+             * See RFC6763 4.1.1: service instance names are UTF-8 and up to 63 bytes. Truncating
+             * names used in registerService follows historical behavior (see mdnsresponder
+             * handle_regservice_request).
+             */
+            @NonNull
+            private String truncateServiceName(@NonNull String originalName) {
+                // UTF-8 is at most 4 bytes per character; return early in the common case where
+                // the name can't possibly be over the limit given its string length.
+                if (originalName.length() <= MAX_LABEL_LENGTH / 4) return originalName;
+
+                final Charset utf8 = StandardCharsets.UTF_8;
+                final CharsetEncoder encoder = utf8.newEncoder();
+                final ByteBuffer out = ByteBuffer.allocate(MAX_LABEL_LENGTH);
+                // encode will write as many characters as possible to the out buffer, and just
+                // return an overflow code if there were too many characters (no need to check the
+                // return code here, this method truncates the name on purpose).
+                encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
+                return new String(out.array(), 0, out.position(), utf8);
             }
 
             @Override
@@ -523,14 +574,16 @@
                                 break;
                             }
 
+                            final String listenServiceType = serviceType + ".local";
                             maybeStartMonitoringSockets();
                             final MdnsListener listener =
-                                    new DiscoveryListener(clientId, id, info, serviceType);
+                                    new DiscoveryListener(clientId, id, info, listenServiceType);
                             final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
                                     .setNetwork(info.getNetwork())
                                     .setIsPassiveMode(true)
                                     .build();
-                            mMdnsDiscoveryManager.registerListener(serviceType, listener, options);
+                            mMdnsDiscoveryManager.registerListener(
+                                    listenServiceType, listener, options);
                             storeListenerMap(clientId, id, listener, clientInfo);
                             clientInfo.onDiscoverServicesStarted(clientId, info);
                         } else {
@@ -608,16 +661,36 @@
                             break;
                         }
 
-                        maybeStartDaemon();
                         id = getUniqueId();
-                        if (registerService(id, args.serviceInfo)) {
-                            if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
+                        if (mAdvertiser != null) {
+                            final NsdServiceInfo serviceInfo = args.serviceInfo;
+                            final String serviceType = serviceInfo.getServiceType();
+                            final String registerServiceType = constructServiceType(serviceType);
+                            if (registerServiceType == null) {
+                                Log.e(TAG, "Invalid service type: " + serviceType);
+                                clientInfo.onRegisterServiceFailed(clientId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                                break;
+                            }
+                            serviceInfo.setServiceType(registerServiceType);
+                            serviceInfo.setServiceName(truncateServiceName(
+                                    serviceInfo.getServiceName()));
+
+                            maybeStartMonitoringSockets();
+                            mAdvertiser.addService(id, serviceInfo);
                             storeRequestMap(clientId, id, clientInfo, msg.what);
-                            // Return success after mDns reports success
                         } else {
-                            unregisterService(id);
-                            clientInfo.onRegisterServiceFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            maybeStartDaemon();
+                            if (registerService(id, args.serviceInfo)) {
+                                if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
+                                storeRequestMap(clientId, id, clientInfo, msg.what);
+                                // Return success after mDns reports success
+                            } else {
+                                unregisterService(id);
+                                clientInfo.onRegisterServiceFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
+
                         }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
@@ -633,11 +706,17 @@
                         }
                         id = clientInfo.mClientIds.get(clientId);
                         removeRequestMap(clientId, id, clientInfo);
-                        if (unregisterService(id)) {
+
+                        if (mAdvertiser != null) {
+                            mAdvertiser.removeService(id);
                             clientInfo.onUnregisterServiceSucceeded(clientId);
                         } else {
-                            clientInfo.onUnregisterServiceFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            if (unregisterService(id)) {
+                                clientInfo.onUnregisterServiceSucceeded(clientId);
+                            } else {
+                                clientInfo.onUnregisterServiceFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
                         }
                         break;
                     case NsdManager.RESOLVE_SERVICE: {
@@ -661,15 +740,17 @@
                                         NsdManager.FAILURE_INTERNAL_ERROR);
                                 break;
                             }
+                            final String resolveServiceType = serviceType + ".local";
 
                             maybeStartMonitoringSockets();
                             final MdnsListener listener =
-                                    new ResolutionListener(clientId, id, info, serviceType);
+                                    new ResolutionListener(clientId, id, info, resolveServiceType);
                             final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
                                     .setNetwork(info.getNetwork())
                                     .setIsPassiveMode(true)
                                     .build();
-                            mMdnsDiscoveryManager.registerListener(serviceType, listener, options);
+                            mMdnsDiscoveryManager.registerListener(
+                                    resolveServiceType, listener, options);
                             storeListenerMap(clientId, id, listener, clientInfo);
                         } else {
                             if (clientInfo.mResolvedService != null) {
@@ -689,6 +770,29 @@
                         }
                         break;
                     }
+                    case NsdManager.STOP_RESOLUTION:
+                        if (DBG) Log.d(TAG, "Stop service resolution");
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
+                        // If the binder death notification for a INsdManagerCallback was received
+                        // before any calls are received by NsdService, the clientInfo would be
+                        // cleared and cause NPE. Add a null check here to prevent this corner case.
+                        if (clientInfo == null) {
+                            Log.e(TAG, "Unknown connector in stop resolution");
+                            break;
+                        }
+
+                        id = clientInfo.mClientIds.get(clientId);
+                        removeRequestMap(clientId, id, clientInfo);
+                        if (stopResolveService(id)) {
+                            clientInfo.onStopResolutionSucceeded(clientId);
+                        } else {
+                            clientInfo.onStopResolutionFailed(
+                                    clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                        }
+                        clientInfo.mResolvedService = null;
+                        // TODO: Implement the stop resolution with MdnsDiscoveryManager.
+                        break;
                     case MDNS_SERVICE_EVENT:
                         if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
                             return NOT_HANDLED;
@@ -1005,18 +1109,32 @@
         mNsdStateMachine.start();
         mMDnsManager = ctx.getSystemService(MDnsManager.class);
         mMDnsEventCallback = new MDnsEventCallback(mNsdStateMachine);
-        if (deps.isMdnsDiscoveryManagerEnabled(ctx)) {
+
+        final boolean discoveryManagerEnabled = deps.isMdnsDiscoveryManagerEnabled(ctx);
+        final boolean advertiserEnabled = deps.isMdnsAdvertiserEnabled(ctx);
+        if (discoveryManagerEnabled || advertiserEnabled) {
             mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper());
+        } else {
+            mMdnsSocketProvider = null;
+        }
+
+        if (discoveryManagerEnabled) {
             mMdnsSocketClient =
                     new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
             mMdnsDiscoveryManager =
                     deps.makeMdnsDiscoveryManager(new ExecutorProvider(), mMdnsSocketClient);
             handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
         } else {
-            mMdnsSocketProvider = null;
             mMdnsSocketClient = null;
             mMdnsDiscoveryManager = null;
         }
+
+        if (advertiserEnabled) {
+            mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
+                    new AdvertiserCallback());
+        } else {
+            mAdvertiser = null;
+        }
     }
 
     /**
@@ -1025,10 +1143,10 @@
     @VisibleForTesting
     public static class Dependencies {
         /**
-         * Check whether or not MdnsDiscoveryManager feature is enabled.
+         * Check whether the MdnsDiscoveryManager feature is enabled.
          *
          * @param context The global context information about an app environment.
-         * @return true if MdnsDiscoveryManager feature is enabled.
+         * @return true if the MdnsDiscoveryManager feature is enabled.
          */
         public boolean isMdnsDiscoveryManagerEnabled(Context context) {
             return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
@@ -1036,6 +1154,17 @@
         }
 
         /**
+         * Check whether the MdnsAdvertiser feature is enabled.
+         *
+         * @param context The global context information about an app environment.
+         * @return true if the MdnsAdvertiser feature is enabled.
+         */
+        public boolean isMdnsAdvertiserEnabled(Context context) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY,
+                    MDNS_ADVERTISER_VERSION, false /* defaultEnabled */);
+        }
+
+        /**
          * @see MdnsDiscoveryManager
          */
         public MdnsDiscoveryManager makeMdnsDiscoveryManager(
@@ -1044,6 +1173,15 @@
         }
 
         /**
+         * @see MdnsAdvertiser
+         */
+        public MdnsAdvertiser makeMdnsAdvertiser(
+                @NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+                @NonNull MdnsAdvertiser.AdvertiserCallback cb) {
+            return new MdnsAdvertiser(looper, socketProvider, cb);
+        }
+
+        /**
          * @see MdnsSocketProvider
          */
         public MdnsSocketProvider makeMdnsSocketProvider(Context context, Looper looper) {
@@ -1101,6 +1239,49 @@
         }
     }
 
+    private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback {
+        @Override
+        public void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo) {
+            final ClientInfo clientInfo = getClientInfoOrLog(serviceId);
+            if (clientInfo == null) return;
+
+            final int clientId = getClientIdOrLog(clientInfo, serviceId);
+            if (clientId < 0) return;
+
+            // onRegisterServiceSucceeded only has the service name in its info. This aligns with
+            // historical behavior.
+            final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
+            clientInfo.onRegisterServiceSucceeded(clientId, cbInfo);
+        }
+
+        @Override
+        public void onRegisterServiceFailed(int serviceId, int errorCode) {
+            final ClientInfo clientInfo = getClientInfoOrLog(serviceId);
+            if (clientInfo == null) return;
+
+            final int clientId = getClientIdOrLog(clientInfo, serviceId);
+            if (clientId < 0) return;
+
+            clientInfo.onRegisterServiceFailed(clientId, errorCode);
+        }
+
+        private ClientInfo getClientInfoOrLog(int serviceId) {
+            final ClientInfo clientInfo = mIdToClientInfoMap.get(serviceId);
+            if (clientInfo == null) {
+                Log.e(TAG, String.format("Callback for service %d has no client", serviceId));
+            }
+            return clientInfo;
+        }
+
+        private int getClientIdOrLog(@NonNull ClientInfo info, int serviceId) {
+            final int clientId = info.getClientId(serviceId);
+            if (clientId < 0) {
+                Log.e(TAG, String.format("Client ID not found for service %d", serviceId));
+            }
+            return clientId;
+        }
+    }
+
     @Override
     public INsdServiceConnector connect(INsdManagerCallback cb) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
@@ -1156,6 +1337,12 @@
         }
 
         @Override
+        public void stopResolution(int listenerKey) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.STOP_RESOLUTION, 0, listenerKey, new ListenerArgs(this, null)));
+        }
+
+        @Override
         public void startDaemon() {
             mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
                     NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
@@ -1364,7 +1551,11 @@
                         stopResolveService(globalId);
                         break;
                     case NsdManager.REGISTER_SERVICE:
-                        unregisterService(globalId);
+                        if (mAdvertiser != null) {
+                            mAdvertiser.removeService(globalId);
+                        } else {
+                            unregisterService(globalId);
+                        }
                         break;
                     default:
                         break;
@@ -1488,5 +1679,21 @@
                 Log.e(TAG, "Error calling onResolveServiceSucceeded", e);
             }
         }
+
+        void onStopResolutionFailed(int listenerKey, int error) {
+            try {
+                mCb.onStopResolutionFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopResolutionFailed", e);
+            }
+        }
+
+        void onStopResolutionSucceeded(int listenerKey) {
+            try {
+                mCb.onStopResolutionSucceeded(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopResolutionSucceeded", e);
+            }
+        }
     }
 }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a7e6a2e..87ac0a8 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -101,6 +101,7 @@
 import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
+import static com.android.server.connectivity.KeepaliveTracker.PERMISSION;
 
 import static java.util.Map.Entry;
 
@@ -269,6 +270,7 @@
 import com.android.networkstack.apishim.common.BroadcastOptionsShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
@@ -276,7 +278,6 @@
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.DscpPolicyTracker;
 import com.android.server.connectivity.FullScore;
-import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.MultinetworkPolicyTracker;
@@ -843,7 +844,7 @@
 
     private final LocationPermissionChecker mLocationPermissionChecker;
 
-    private final KeepaliveTracker mKeepaliveTracker;
+    private final AutomaticOnOffKeepaliveTracker mKeepaliveTracker;
     private final QosCallbackTracker mQosCallbackTracker;
     private final NetworkNotificationManager mNotifier;
     private final LingerMonitor mLingerMonitor;
@@ -1565,7 +1566,7 @@
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
 
-        mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
+        mKeepaliveTracker = new AutomaticOnOffKeepaliveTracker(mContext, mHandler);
         mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
         mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
 
@@ -2998,7 +2999,7 @@
     }
 
     private void enforceKeepalivePermission() {
-        mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "ConnectivityService");
     }
 
     private boolean checkLocalMacAddressPermission(int pid, int uid) {
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
new file mode 100644
index 0000000..85ec5e3
--- /dev/null
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.INetd;
+import android.net.ISocketKeepaliveCallback;
+import android.net.MarkMaskParcel;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SocketUtils;
+import com.android.net.module.util.netlink.InetDiagMessage;
+import com.android.net.module.util.netlink.NetlinkUtils;
+import com.android.net.module.util.netlink.StructNlAttr;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages automatic on/off socket keepalive requests.
+ *
+ * Provides methods to stop and start automatic keepalive requests, and keeps track of keepalives
+ * across all networks. For non-automatic on/off keepalive request, this class bypass the requests
+ * and send to KeepaliveTrakcer. This class is tightly coupled to ConnectivityService. It is not
+ * thread-safe and its handle* methods must be called only from the ConnectivityService handler
+ * thread.
+ */
+public class AutomaticOnOffKeepaliveTracker {
+    private static final String TAG = "AutomaticOnOffKeepaliveTracker";
+    private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
+
+    @NonNull
+    private final Handler mConnectivityServiceHandler;
+    @NonNull
+    private final KeepaliveTracker mKeepaliveTracker;
+    @NonNull
+    private final Context mContext;
+
+    /**
+     * The {@code inetDiagReqV2} messages for different IP family.
+     *
+     *   Key: Ip family type.
+     * Value: Bytes array represent the {@code inetDiagReqV2}.
+     *
+     * This should only be accessed in the connectivity service handler thread.
+     */
+    private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
+    private final Dependencies mDependencies;
+    private final INetd mNetd;
+
+    public AutomaticOnOffKeepaliveTracker(Context context, Handler handler) {
+        this(context, handler, new Dependencies(context));
+    }
+
+    @VisibleForTesting
+    public AutomaticOnOffKeepaliveTracker(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Dependencies dependencies) {
+        mContext = Objects.requireNonNull(context);
+        mDependencies = dependencies;
+        this.mConnectivityServiceHandler = Objects.requireNonNull(handler);
+        mNetd = mDependencies.getNetd();
+        mKeepaliveTracker = mDependencies.newKeepaliveTracker(
+                mContext, mConnectivityServiceHandler);
+    }
+
+    /**
+     * Handle keepalive events from lower layer.
+     */
+    public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
+        mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason);
+    }
+
+    /**
+     * Handle stop all keepalives on the specific network.
+     */
+    public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
+        mKeepaliveTracker.handleStopAllKeepalives(nai, reason);
+    }
+
+    /**
+     *  Handle start keepalives with the message.
+     *
+     *  The message is expected to be a KeepaliveTracker.KeepaliveInfo.
+     */
+    public void handleStartKeepalive(Message message) {
+        mKeepaliveTracker.handleStartKeepalive(message);
+    }
+
+    /**
+     * Handle stop keepalives on the specific network with given slot.
+     */
+    public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
+        mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
+    }
+
+    /**
+     * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+     * {@link android.net.SocketKeepalive}.
+     **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            @Nullable FileDescriptor fd,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb,
+            @NonNull String srcAddrString,
+            int srcPort,
+            @NonNull String dstAddrString,
+            int dstPort) {
+        mKeepaliveTracker.startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString,
+                srcPort, dstAddrString, dstPort);
+    }
+
+    /**
+     * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+     * {@link android.net.SocketKeepalive}.
+     **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            @Nullable FileDescriptor fd,
+            int resourceId,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb,
+            @NonNull String srcAddrString,
+            @NonNull String dstAddrString,
+            int dstPort) {
+        mKeepaliveTracker.startNattKeepalive(nai, fd, resourceId, intervalSeconds, cb,
+                srcAddrString, dstAddrString, dstPort);
+    }
+
+    /**
+     * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+     *
+     * In order to offload keepalive for application correctly, sequence number, ack number and
+     * other fields are needed to form the keepalive packet. Thus, this function synchronously
+     * puts the socket into repair mode to get the necessary information. After the socket has been
+     * put into repair mode, the application cannot access the socket until reverted to normal.
+     *
+     * See {@link android.net.SocketKeepalive}.
+     **/
+    public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+            @NonNull FileDescriptor fd,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb) {
+        mKeepaliveTracker.startTcpKeepalive(nai, fd, intervalSeconds, cb);
+    }
+
+    /**
+     * Dump AutomaticOnOffKeepaliveTracker state.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        // TODO:  Dump the necessary information for automatic on/off keepalive.
+        mKeepaliveTracker.dump(pw);
+    }
+
+    /**
+     * Check all keeplaives on the network are still valid.
+     */
+    public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
+        mKeepaliveTracker.handleCheckKeepalivesStillValid(nai);
+    }
+
+    @VisibleForTesting
+    boolean isAnyTcpSocketConnected(int netId) {
+        FileDescriptor fd = null;
+
+        try {
+            fd = mDependencies.createConnectedNetlinkSocket();
+
+            // Get network mask
+            final MarkMaskParcel parcel = mNetd.getFwmarkForNetwork(netId);
+            final int networkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK;
+            final int networkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK;
+
+            // Send request for each IP family
+            for (final int family : ADDRESS_FAMILIES) {
+                if (isAnyTcpSocketConnectedForFamily(fd, family, networkMark, networkMask)) {
+                    return true;
+                }
+            }
+        } catch (ErrnoException | SocketException | InterruptedIOException | RemoteException e) {
+            Log.e(TAG, "Fail to get socket info via netlink.", e);
+        } finally {
+            SocketUtils.closeSocketQuietly(fd);
+        }
+
+        return false;
+    }
+
+    private boolean isAnyTcpSocketConnectedForFamily(FileDescriptor fd, int family, int networkMark,
+            int networkMask) throws ErrnoException, InterruptedIOException {
+        ensureRunningOnHandlerThread();
+        // Build SocketDiag messages and cache it.
+        if (mSockDiagMsg.get(family) == null) {
+            mSockDiagMsg.put(family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
+        }
+        mDependencies.sendRequest(fd, mSockDiagMsg.get(family));
+
+        // Iteration limitation as a protection to avoid possible infinite loops.
+        // DEFAULT_RECV_BUFSIZE could read more than 20 sockets per time. Max iteration
+        // should be enough to go through reasonable TCP sockets in the device.
+        final int maxIteration = 100;
+        int parsingIteration = 0;
+        while (parsingIteration < maxIteration) {
+            final ByteBuffer bytes = mDependencies.recvSockDiagResponse(fd);
+
+            try {
+                while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
+                    final int startPos = bytes.position();
+
+                    final int nlmsgLen = bytes.getInt();
+                    final int nlmsgType = bytes.getShort();
+                    if (isEndOfMessageOrError(nlmsgType)) return false;
+                    // TODO: Parse InetDiagMessage to get uid and dst address information to filter
+                    //  socket via NetlinkMessage.parse.
+
+                    // Skip the header to move to data part.
+                    bytes.position(startPos + SOCKDIAG_MSG_HEADER_SIZE);
+
+                    if (isTargetTcpSocket(bytes, nlmsgLen, networkMark, networkMask)) {
+                        return true;
+                    }
+                }
+            } catch (BufferUnderflowException e) {
+                // The exception happens in random place in either header position or any data
+                // position. Partial bytes from the middle of the byte buffer may not be enough to
+                // clarify, so print out the content before the error to possibly prevent printing
+                // the whole 8K buffer.
+                final int exceptionPos = bytes.position();
+                final String hex = HexDump.dumpHexString(bytes.array(), 0, exceptionPos);
+                Log.e(TAG, "Unexpected socket info parsing: " + hex, e);
+            }
+
+            parsingIteration++;
+        }
+        return false;
+    }
+
+    private boolean isEndOfMessageOrError(int nlmsgType) {
+        return nlmsgType == NLMSG_DONE || nlmsgType != SOCK_DIAG_BY_FAMILY;
+    }
+
+    private boolean isTargetTcpSocket(@NonNull ByteBuffer bytes, int nlmsgLen, int networkMark,
+            int networkMask) {
+        final int mark = readSocketDataAndReturnMark(bytes, nlmsgLen);
+        return (mark & networkMask) == networkMark;
+    }
+
+    private int readSocketDataAndReturnMark(@NonNull ByteBuffer bytes, int nlmsgLen) {
+        final int nextMsgOffset = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE;
+        int mark = NetlinkUtils.INIT_MARK_VALUE;
+        // Get socket mark
+        // TODO: Add a parsing method in NetlinkMessage.parse to support this to skip the remaining
+        //  data.
+        while (bytes.position() < nextMsgOffset) {
+            final StructNlAttr nlattr = StructNlAttr.parse(bytes);
+            if (nlattr != null && nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
+                mark = nlattr.getValueAsInteger();
+            }
+        }
+        return mark;
+    }
+
+    private void ensureRunningOnHandlerThread() {
+        if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on handler thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    /**
+     * Dependencies class for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        private final Context mContext;
+
+        public Dependencies(final Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Create a netlink socket connected to the kernel.
+         *
+         * @return fd the fileDescriptor of the socket.
+         */
+        public FileDescriptor createConnectedNetlinkSocket()
+                throws ErrnoException, SocketException {
+            final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
+            NetlinkUtils.connectSocketToNetlink(fd);
+            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
+                    StructTimeval.fromMillis(IO_TIMEOUT_MS));
+            return fd;
+        }
+
+        /**
+         * Send composed message request to kernel.
+         *
+         * The given FileDescriptor is expected to be created by
+         * {@link #createConnectedNetlinkSocket} or equivalent way.
+         *
+         * @param fd a netlink socket {@code FileDescriptor} connected to the kernel.
+         * @param msg the byte array representing the request message to write to kernel.
+         */
+        public void sendRequest(@NonNull final FileDescriptor fd,
+                @NonNull final byte[] msg)
+                throws ErrnoException, InterruptedIOException {
+            Os.write(fd, msg, 0 /* byteOffset */, msg.length);
+        }
+
+        /**
+         * Get an INetd connector.
+         */
+        public INetd getNetd() {
+            return INetd.Stub.asInterface(
+                    (IBinder) mContext.getSystemService(Context.NETD_SERVICE));
+        }
+
+        /**
+         * Receive the response message from kernel via given {@code FileDescriptor}.
+         * The usage should follow the {@code #sendRequest} call with the same
+         * FileDescriptor.
+         *
+         * The overall response may be large but the individual messages should not be
+         * excessively large(8-16kB) because trying to get the kernel to return
+         * everything in one big buffer is inefficient as it forces the kernel to allocate
+         * large chunks of linearly physically contiguous memory. The usage should iterate the
+         * call of this method until the end of the overall message.
+         *
+         * The default receiving buffer size should be small enough that it is always
+         * processed within the {@link NetlinkUtils#IO_TIMEOUT_MS} timeout.
+         */
+        public ByteBuffer recvSockDiagResponse(@NonNull final FileDescriptor fd)
+                throws ErrnoException, InterruptedIOException {
+            return NetlinkUtils.recvMessage(
+                    fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, NetlinkUtils.IO_TIMEOUT_MS);
+        }
+
+        /**
+         * Construct a new KeepaliveTracker.
+         */
+        public KeepaliveTracker newKeepaliveTracker(@NonNull Context context,
+                @NonNull Handler connectivityserviceHander) {
+            return new KeepaliveTracker(mContext, connectivityserviceHander);
+        }
+    }
+}
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 9c36760..23fdfd4 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -33,27 +33,15 @@
 import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
 import static android.net.SocketKeepalive.NO_KEEPALIVE;
 import static android.net.SocketKeepalive.SUCCESS;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_SNDTIMEO;
-
-import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
-import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
-import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
-import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.Resources;
 import android.net.ConnectivityResources;
-import android.net.INetd;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
 import android.net.InvalidPacketException;
 import android.net.KeepalivePacketData;
-import android.net.MarkMaskParcel;
 import android.net.NattKeepalivePacketData;
 import android.net.NetworkAgent;
 import android.net.SocketKeepalive.InvalidSocketException;
@@ -67,29 +55,18 @@
 import android.os.RemoteException;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.StructTimeval;
 import android.util.Log;
 import android.util.Pair;
-import android.util.SparseArray;
 
 import com.android.connectivity.resources.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.HexDump;
 import com.android.net.module.util.IpUtils;
-import com.android.net.module.util.SocketUtils;
-import com.android.net.module.util.netlink.InetDiagMessage;
-import com.android.net.module.util.netlink.NetlinkUtils;
-import com.android.net.module.util.netlink.StructNlAttr;
 
 import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
-import java.net.SocketException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -107,7 +84,6 @@
     private static final boolean DBG = false;
 
     public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
-    private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
 
     /** Keeps track of keepalive requests. */
     private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
@@ -131,35 +107,18 @@
     // Allowed unprivileged keepalive slots per uid. Caller's permission will be enforced if
     // the number of remaining keepalive slots is less than or equal to the threshold.
     private final int mAllowedUnprivilegedSlotsForUid;
-    /**
-     * The {@code inetDiagReqV2} messages for different IP family.
-     *
-     *   Key: Ip family type.
-     * Value: Bytes array represent the {@code inetDiagReqV2}.
-     *
-     * This should only be accessed in the connectivity service handler thread.
-     */
-    private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
-    private final Dependencies mDependencies;
-    private final INetd mNetd;
 
     public KeepaliveTracker(Context context, Handler handler) {
-        this(context, handler, new Dependencies(context));
-    }
-
-    @VisibleForTesting
-    public KeepaliveTracker(Context context, Handler handler, Dependencies dependencies) {
         mConnectivityServiceHandler = handler;
         mTcpController = new TcpKeepaliveController(handler);
         mContext = context;
-        mDependencies = dependencies;
-        mSupportedKeepalives = mDependencies.getSupportedKeepalives();
-        mNetd = mDependencies.getNetd();
 
-        final Resources res = mDependencies.newConnectivityResources();
-        mReservedPrivilegedSlots = res.getInteger(
+        mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
+
+        final ConnectivityResources res = new ConnectivityResources(mContext);
+        mReservedPrivilegedSlots = res.get().getInteger(
                 R.integer.config_reservedPrivilegedKeepaliveSlots);
-        mAllowedUnprivilegedSlotsForUid = res.getInteger(
+        mAllowedUnprivilegedSlotsForUid = res.get().getInteger(
                 R.integer.config_allowedUnprivilegedKeepalivePerUid);
     }
 
@@ -801,196 +760,4 @@
         }
         pw.decreaseIndent();
     }
-
-    /**
-     * Dependencies class for testing.
-     */
-    @VisibleForTesting
-    public static class Dependencies {
-        private final Context mContext;
-
-        public Dependencies(final Context context) {
-            mContext = context;
-        }
-
-        /**
-         * Create a netlink socket connected to the kernel.
-         *
-         * @return fd the fileDescriptor of the socket.
-         */
-        public FileDescriptor createConnectedNetlinkSocket()
-                throws ErrnoException, SocketException {
-            final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
-            NetlinkUtils.connectSocketToNetlink(fd);
-            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
-                    StructTimeval.fromMillis(IO_TIMEOUT_MS));
-            return fd;
-        }
-
-        /**
-         * Send composed message request to kernel.
-         *
-         * The given FileDescriptor is expected to be created by
-         * {@link #createConnectedNetlinkSocket} or equivalent way.
-         *
-         * @param fd a netlink socket {@code FileDescriptor} connected to the kernel.
-         * @param msg the byte array representing the request message to write to kernel.
-         */
-        public void sendRequest(@NonNull final FileDescriptor fd,
-                @NonNull final byte[] msg)
-                throws ErrnoException, InterruptedIOException {
-            Os.write(fd, msg, 0 /* byteOffset */, msg.length);
-        }
-
-        /**
-         * Get an INetd connector.
-         */
-        public INetd getNetd() {
-            return INetd.Stub.asInterface(
-                    (IBinder) mContext.getSystemService(Context.NETD_SERVICE));
-        }
-
-        /**
-         * Receive the response message from kernel via given {@code FileDescriptor}.
-         * The usage should follow the {@code #sendRequest} call with the same
-         * FileDescriptor.
-         *
-         * The overall response may be large but the individual messages should not be
-         * excessively large(8-16kB) because trying to get the kernel to return
-         * everything in one big buffer is inefficient as it forces the kernel to allocate
-         * large chunks of linearly physically contiguous memory. The usage should iterate the
-         * call of this method until the end of the overall message.
-         *
-         * The default receiving buffer size should be small enough that it is always
-         * processed within the {@link NetlinkUtils#IO_TIMEOUT_MS} timeout.
-         */
-        public ByteBuffer recvSockDiagResponse(@NonNull final FileDescriptor fd)
-                throws ErrnoException, InterruptedIOException {
-            return NetlinkUtils.recvMessage(
-                    fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, NetlinkUtils.IO_TIMEOUT_MS);
-        }
-
-        /**
-         * Read supported keepalive count for each transport type from overlay resource.
-         */
-        public int[] getSupportedKeepalives() {
-            return KeepaliveUtils.getSupportedKeepalives(mContext);
-        }
-
-        /**
-         * Construct a new Resource from a new ConnectivityResources.
-         */
-        public Resources newConnectivityResources() {
-            final ConnectivityResources resources = new ConnectivityResources(mContext);
-            return resources.get();
-        }
-    }
-
-    private void ensureRunningOnHandlerThread() {
-        if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
-            throw new IllegalStateException(
-                    "Not running on handler thread: " + Thread.currentThread().getName());
-        }
-    }
-
-    @VisibleForTesting
-    boolean isAnyTcpSocketConnected(int netId) {
-        FileDescriptor fd = null;
-
-        try {
-            fd = mDependencies.createConnectedNetlinkSocket();
-
-            // Get network mask
-            final MarkMaskParcel parcel = mNetd.getFwmarkForNetwork(netId);
-            final int networkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK;
-            final int networkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK;
-
-            // Send request for each IP family
-            for (final int family : ADDRESS_FAMILIES) {
-                if (isAnyTcpSocketConnectedForFamily(fd, family, networkMark, networkMask)) {
-                    return true;
-                }
-            }
-        } catch (ErrnoException | SocketException | InterruptedIOException | RemoteException e) {
-            Log.e(TAG, "Fail to get socket info via netlink.", e);
-        } finally {
-            SocketUtils.closeSocketQuietly(fd);
-        }
-
-        return false;
-    }
-
-    private boolean isAnyTcpSocketConnectedForFamily(FileDescriptor fd, int family, int networkMark,
-            int networkMask) throws ErrnoException, InterruptedIOException {
-        ensureRunningOnHandlerThread();
-        // Build SocketDiag messages and cache it.
-        if (mSockDiagMsg.get(family) == null) {
-            mSockDiagMsg.put(family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
-        }
-        mDependencies.sendRequest(fd, mSockDiagMsg.get(family));
-
-        // Iteration limitation as a protection to avoid possible infinite loops.
-        // DEFAULT_RECV_BUFSIZE could read more than 20 sockets per time. Max iteration
-        // should be enough to go through reasonable TCP sockets in the device.
-        final int maxIteration = 100;
-        int parsingIteration = 0;
-        while (parsingIteration < maxIteration) {
-            final ByteBuffer bytes = mDependencies.recvSockDiagResponse(fd);
-
-            try {
-                while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
-                    final int startPos = bytes.position();
-
-                    final int nlmsgLen = bytes.getInt();
-                    final int nlmsgType = bytes.getShort();
-                    if (isEndOfMessageOrError(nlmsgType)) return false;
-                    // TODO: Parse InetDiagMessage to get uid and dst address information to filter
-                    //  socket via NetlinkMessage.parse.
-
-                    // Skip the header to move to data part.
-                    bytes.position(startPos + SOCKDIAG_MSG_HEADER_SIZE);
-
-                    if (isTargetTcpSocket(bytes, nlmsgLen, networkMark, networkMask)) {
-                        return true;
-                    }
-                }
-            } catch (BufferUnderflowException e) {
-                // The exception happens in random place in either header position or any data
-                // position. Partial bytes from the middle of the byte buffer may not be enough to
-                // clarify, so print out the content before the error to possibly prevent printing
-                // the whole 8K buffer.
-                final int exceptionPos = bytes.position();
-                final String hex = HexDump.dumpHexString(bytes.array(), 0, exceptionPos);
-                Log.e(TAG, "Unexpected socket info parsing: " + hex, e);
-            }
-
-            parsingIteration++;
-        }
-        return false;
-    }
-
-    private boolean isEndOfMessageOrError(int nlmsgType) {
-        return nlmsgType == NLMSG_DONE || nlmsgType != SOCK_DIAG_BY_FAMILY;
-    }
-
-    private boolean isTargetTcpSocket(@NonNull ByteBuffer bytes, int nlmsgLen, int networkMark,
-            int networkMask) {
-        final int mark = readSocketDataAndReturnMark(bytes, nlmsgLen);
-        return (mark & networkMask) == networkMark;
-    }
-
-    private int readSocketDataAndReturnMark(@NonNull ByteBuffer bytes, int nlmsgLen) {
-        final int nextMsgOffset = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE;
-        int mark = NetlinkUtils.INIT_MARK_VALUE;
-        // Get socket mark
-        // TODO: Add a parsing method in NetlinkMessage.parse to support this to skip the remaining
-        //  data.
-        while (bytes.position() < nextMsgOffset) {
-            final StructNlAttr nlattr = StructNlAttr.parse(bytes);
-            if (nlattr != null && nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
-                mark = nlattr.getValueAsInteger();
-            }
-        }
-        return mark;
-    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 2b5c305..b7eb009 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -41,7 +41,9 @@
 import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
 import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed
 import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveStopped
 import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.StopResolutionFailed
 import android.net.nsd.NsdManager
 import android.net.nsd.NsdManager.DiscoveryListener
 import android.net.nsd.NsdManager.RegistrationListener
@@ -66,15 +68,6 @@
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import com.android.testutils.waitForIdle
-import org.junit.After
-import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
 import java.io.File
 import java.net.ServerSocket
 import java.nio.charset.StandardCharsets
@@ -86,6 +79,15 @@
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
 
 private const val TAG = "NsdManagerTest"
 private const val TIMEOUT_MS = 2000L
@@ -182,10 +184,10 @@
                 val errorCode: Int
             ) : RegistrationEvent()
 
-            data class ServiceRegistered(override val serviceInfo: NsdServiceInfo)
-                : RegistrationEvent()
-            data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo)
-                : RegistrationEvent()
+            data class ServiceRegistered(override val serviceInfo: NsdServiceInfo) :
+                    RegistrationEvent()
+            data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo) :
+                    RegistrationEvent()
         }
 
         override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
@@ -208,11 +210,11 @@
     private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
             DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
         sealed class DiscoveryEvent : NsdEvent {
-            data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
-                : DiscoveryEvent()
+            data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+                    DiscoveryEvent()
 
-            data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int)
-                : DiscoveryEvent()
+            data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+                    DiscoveryEvent()
 
             data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
             data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
@@ -259,10 +261,13 @@
     private class NsdResolveRecord : ResolveListener,
             NsdRecord<NsdResolveRecord.ResolveEvent>() {
         sealed class ResolveEvent : NsdEvent {
-            data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int)
-                : ResolveEvent()
+            data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+                    ResolveEvent()
 
             data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+            data class ResolveStopped(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+            data class StopResolutionFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+                    ResolveEvent()
         }
 
         override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
@@ -272,6 +277,14 @@
         override fun onServiceResolved(si: NsdServiceInfo) {
             add(ServiceResolved(si))
         }
+
+        override fun onResolveStopped(si: NsdServiceInfo) {
+            add(ResolveStopped(si))
+        }
+
+        override fun onStopResolutionFailed(si: NsdServiceInfo, err: Int) {
+            add(StopResolutionFailed(si, err))
+        }
     }
 
     @Before
@@ -739,6 +752,26 @@
                 NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
     }
 
+    @Test
+    fun testStopServiceResolution() {
+        // This test requires shims supporting U+ APIs (NsdManager.stopServiceResolution)
+        assumeTrue(TestUtils.shouldTestUApis())
+
+        val si = NsdServiceInfo()
+        si.serviceType = this@NsdManagerTest.serviceType
+        si.serviceName = this@NsdManagerTest.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val resolveRecord = NsdResolveRecord()
+        // Try to resolve an unknown service then stop it immediately.
+        // Expected ResolveStopped callback.
+        nsdShim.resolveService(nsdManager, si, { it.run() }, resolveRecord)
+        nsdShim.stopServiceResolution(nsdManager, resolveRecord)
+        val stoppedCb = resolveRecord.expectCallback<ResolveStopped>()
+        assertEquals(si.serviceName, stoppedCb.serviceInfo.serviceName)
+        assertEquals(si.serviceType, stoppedCb.serviceInfo.serviceType)
+    }
+
     /**
      * Register a service and return its registration record.
      */
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 1bd49a5..a8f8121 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -16,7 +16,9 @@
 
 package com.android.server;
 
+import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
 
 import static com.android.testutils.ContextUtils.mockService;
 
@@ -47,7 +49,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.INetd;
-import android.net.InetAddresses;
 import android.net.Network;
 import android.net.mdns.aidl.DiscoveryInfo;
 import android.net.mdns.aidl.GetAddressInfo;
@@ -69,11 +70,13 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.NsdService.Dependencies;
+import com.android.server.connectivity.mdns.MdnsAdvertiser;
 import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
 import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
 import com.android.server.connectivity.mdns.MdnsServiceInfo;
@@ -96,6 +99,7 @@
 import java.net.UnknownHostException;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Queue;
 
 // TODOs:
@@ -129,6 +133,7 @@
     @Mock MDnsManager mMockMDnsM;
     @Mock Dependencies mDeps;
     @Mock MdnsDiscoveryManager mDiscoveryManager;
+    @Mock MdnsAdvertiser mAdvertiser;
     @Mock MdnsSocketProvider mSocketProvider;
     HandlerThread mThread;
     TestHandler mHandler;
@@ -399,7 +404,7 @@
         final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
         assertEquals(SERVICE_NAME, resolvedService.getServiceName());
         assertEquals("." + SERVICE_TYPE, resolvedService.getServiceType());
-        assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
+        assertEquals(parseNumericAddress(serviceAddress), resolvedService.getHost());
         assertEquals(servicePort, resolvedService.getPort());
         assertNull(resolvedService.getNetwork());
         assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
@@ -572,6 +577,95 @@
                 anyInt()/* interfaceIdx */);
     }
 
+    @Test
+    public void testStopServiceResolution() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        final int resolveId = resolvIdCaptor.getValue();
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(resolveId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolveStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+    }
+
+    @Test
+    public void testStopResolutionFailed() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        final int resolveId = resolvIdCaptor.getValue();
+        doReturn(false).when(mMockMDnsM).stopOperation(anyInt());
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(resolveId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onStopResolutionFailed(argThat(ns ->
+                        request.getServiceName().equals(ns.getServiceName())
+                                && request.getServiceType().equals(ns.getServiceType())),
+                eq(FAILURE_OPERATION_NOT_RUNNING));
+    }
+
+    @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testStopResolutionDuringGettingAddress() throws RemoteException {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final IMDnsEventListener eventListener = getEventListener();
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        // Resolve service successfully.
+        final ResolutionInfo resolutionInfo = new ResolutionInfo(
+                resolvIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_RESOLVED,
+                null /* serviceName */,
+                null /* serviceType */,
+                null /* domain */,
+                SERVICE_FULL_NAME,
+                DOMAIN_NAME,
+                PORT,
+                new byte[0] /* txtRecord */,
+                IFACE_IDX_ANY);
+        doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
+        eventListener.onServiceResolutionStatus(resolutionInfo);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(DOMAIN_NAME),
+                eq(IFACE_IDX_ANY));
+
+        final int getAddrId = getAddrIdCaptor.getValue();
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(getAddrId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolveStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+    }
+
     private void makeServiceWithMdnsDiscoveryManagerEnabled() {
         doReturn(true).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
         doReturn(mDiscoveryManager).when(mDeps).makeMdnsDiscoveryManager(any(), any());
@@ -582,6 +676,16 @@
         verify(mDeps).makeMdnsSocketProvider(any(), any());
     }
 
+    private void makeServiceWithMdnsAdvertiserEnabled() {
+        doReturn(true).when(mDeps).isMdnsAdvertiserEnabled(any(Context.class));
+        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any());
+        doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any());
+
+        mService = makeService();
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), any());
+        verify(mDeps).makeMdnsSocketProvider(any(), any());
+    }
+
     @Test
     public void testMdnsDiscoveryManagerFeature() {
         // Create NsdService w/o feature enabled.
@@ -733,7 +837,7 @@
         assertTrue(info.getAttributes().containsKey("key"));
         assertEquals(1, info.getAttributes().size());
         assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key"));
-        assertEquals(InetAddresses.parseNumericAddress(IPV4_ADDRESS), info.getHost());
+        assertEquals(parseNumericAddress(IPV4_ADDRESS), info.getHost());
         assertEquals(network, info.getNetwork());
 
         // Verify the listener has been unregistered.
@@ -742,6 +846,102 @@
         verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets();
     }
 
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser() {
+        makeServiceWithMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAdvertiser).addService(idCaptor.capture(), argThat(info ->
+                matches(info, regInfo)));
+
+        // Verify onServiceRegistered callback
+        final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(argThat(info -> matches(info,
+                new NsdServiceInfo(regInfo.getServiceName(), null))));
+
+        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)).stopMonitoringSockets();
+    }
+
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser_FailedWithInvalidServiceType() {
+        makeServiceWithMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, "invalid_type");
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mAdvertiser, never()).addService(anyInt(), any());
+
+        verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
+                argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
+    }
+
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser_LongServiceName() {
+        makeServiceWithMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+        // Service name is truncated to 63 characters
+        verify(mAdvertiser).addService(idCaptor.capture(),
+                argThat(info -> info.getServiceName().equals("a".repeat(63))));
+
+        // Verify onServiceRegistered callback
+        final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
+                argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
@@ -786,6 +986,19 @@
         verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).stopDaemon();
     }
 
+    /**
+     * Return true if two service info are the same.
+     *
+     * Useful for argument matchers as {@link NsdServiceInfo} does not implement equals.
+     */
+    private boolean matches(NsdServiceInfo a, NsdServiceInfo b) {
+        return Objects.equals(a.getServiceName(), b.getServiceName())
+                && Objects.equals(a.getServiceType(), b.getServiceType())
+                && Objects.equals(a.getHost(), b.getHost())
+                && Objects.equals(a.getNetwork(), b.getNetwork())
+                && Objects.equals(a.getAttributes(), b.getAttributes());
+    }
+
     public static class TestHandler extends Handler {
         public Message lastMessage;
 
diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
index c8a93a6..deb56ef 100644
--- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java
+++ b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
@@ -131,6 +131,11 @@
                 Vpn vpn, VpnProfile profile) {
             return mLockdownVpnTracker;
         }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return UserHandle.USER_SYSTEM;
+        }
     }
 
     @Before
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
similarity index 86%
rename from tests/unit/java/com/android/server/connectivity/KeepaliveTrackerTest.java
rename to tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index b55ee67..8c9cfe8 100644
--- a/tests/unit/java/com/android/server/connectivity/KeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -24,14 +24,12 @@
 import static org.mockito.Mockito.doReturn;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.net.INetd;
 import android.net.MarkMaskParcel;
 import android.os.Build;
 import android.os.HandlerThread;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.connectivity.resources.R;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -48,21 +46,19 @@
 
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class KeepaliveTrackerTest {
-    private static final int[] TEST_SUPPORTED_KEEPALIVES = {1, 3, 0, 0, 0, 0, 0, 0, 0};
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+public class AutomaticOnOffKeepaliveTrackerTest {
     private static final int TEST_NETID = 0xA85;
     private static final int TEST_NETID_FWMARK = 0x0A85;
     private static final int OTHER_NETID = 0x1A85;
     private static final int NETID_MASK = 0xffff;
-    private static final int SUPPORTED_SLOT_COUNT = 2;
-    private KeepaliveTracker mKeepaliveTracker;
+    private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
     private HandlerThread mHandlerThread;
 
     @Mock INetd mNetd;
-    @Mock KeepaliveTracker.Dependencies mDependencies;
+    @Mock AutomaticOnOffKeepaliveTracker.Dependencies mDependencies;
     @Mock Context mCtx;
-    @Mock Resources mResources;
+    @Mock KeepaliveTracker mKeepaliveTracker;
 
     // Hexadecimal representation of a SOCK_DIAG response with tcp info.
     private static final String SOCK_DIAG_TCP_INET_HEX =
@@ -169,51 +165,42 @@
         doReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID_FWMARK)).when(mNetd)
                 .getFwmarkForNetwork(TEST_NETID);
 
-        doReturn(TEST_SUPPORTED_KEEPALIVES).when(mDependencies).getSupportedKeepalives();
-        doReturn(mResources).when(mDependencies).newConnectivityResources();
-        mockResource();
         doNothing().when(mDependencies).sendRequest(any(), any());
 
         mHandlerThread = new HandlerThread("KeepaliveTrackerTest");
         mHandlerThread.start();
-
-        mKeepaliveTracker = new KeepaliveTracker(mCtx, mHandlerThread.getThreadHandler(),
-                mDependencies);
-    }
-
-    private void mockResource() {
-        doReturn(SUPPORTED_SLOT_COUNT).when(mResources).getInteger(
-                R.integer.config_reservedPrivilegedKeepaliveSlots);
-        doReturn(SUPPORTED_SLOT_COUNT).when(mResources).getInteger(
-                R.integer.config_allowedUnprivilegedKeepalivePerUid);
+        doReturn(mKeepaliveTracker).when(mDependencies).newKeepaliveTracker(
+                mCtx, mHandlerThread.getThreadHandler());
+        mAOOKeepaliveTracker = new AutomaticOnOffKeepaliveTracker(
+                mCtx, mHandlerThread.getThreadHandler(), mDependencies);
     }
 
     @Test
     public void testIsAnyTcpSocketConnected_runOnNonHandlerThread() throws Exception {
         setupResponseWithSocketExisting();
         assertThrows(IllegalStateException.class,
-                () -> mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID));
+                () -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID));
     }
 
     @Test
     public void testIsAnyTcpSocketConnected_withTargetNetId() throws Exception {
         setupResponseWithSocketExisting();
         mHandlerThread.getThreadHandler().post(
-                () -> assertTrue(mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+                () -> assertTrue(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
     }
 
     @Test
     public void testIsAnyTcpSocketConnected_withIncorrectNetId() throws Exception {
         setupResponseWithSocketExisting();
         mHandlerThread.getThreadHandler().post(
-                () -> assertFalse(mKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
+                () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
     }
 
     @Test
     public void testIsAnyTcpSocketConnected_noSocketExists() throws Exception {
         setupResponseWithoutSocketExisting();
         mHandlerThread.getThreadHandler().post(
-                () -> assertFalse(mKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+                () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
     }
 
     private void setupResponseWithSocketExisting() throws Exception {
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index ebf1a9b..4d4a0b2 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -14,6 +14,13 @@
 //
 // This file is automatically generated by gen_android_bp. Do not edit.
 
+// GN: PACKAGE
+package {
+    default_applicable_licenses: [
+        "external_cronet_license",
+    ],
+}
+
 // GN: //components/cronet/android:cronet_api_java
 java_library {
     name: "cronet_aml_api_java",
@@ -10645,3 +10652,55 @@
     ],
 }
 
+// GN: LICENSE
+license {
+    name: "external_cronet_license",
+    license_kinds: [
+        "SPDX-license-identifier-AFL-2.0",
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-BSL-1.0",
+        "SPDX-license-identifier-GPL",
+        "SPDX-license-identifier-GPL-2.0",
+        "SPDX-license-identifier-GPL-3.0",
+        "SPDX-license-identifier-ICU",
+        "SPDX-license-identifier-ISC",
+        "SPDX-license-identifier-LGPL",
+        "SPDX-license-identifier-LGPL-2.1",
+        "SPDX-license-identifier-MIT",
+        "SPDX-license-identifier-MPL",
+        "SPDX-license-identifier-MPL-2.0",
+        "SPDX-license-identifier-NCSA",
+        "SPDX-license-identifier-OpenSSL",
+        "SPDX-license-identifier-Unicode-DFS",
+        "legacy_unencumbered",
+    ],
+    license_text: [
+        "LICENSE",
+        "base/third_party/double_conversion/LICENSE",
+        "base/third_party/dynamic_annotations/LICENSE",
+        "base/third_party/icu/LICENSE",
+        "base/third_party/nspr/LICENSE",
+        "base/third_party/superfasthash/LICENSE",
+        "base/third_party/symbolize/LICENSE",
+        "base/third_party/valgrind/LICENSE",
+        "base/third_party/xdg_user_dirs/LICENSE",
+        "net/third_party/quiche/src/LICENSE",
+        "net/third_party/uri_template/LICENSE",
+        "third_party/abseil-cpp/LICENSE",
+        "third_party/ashmem/LICENSE",
+        "third_party/boringssl/src/LICENSE",
+        "third_party/boringssl/src/third_party/fiat/LICENSE",
+        "third_party/boringssl/src/third_party/googletest/LICENSE",
+        "third_party/boringssl/src/third_party/wycheproof_testvectors/LICENSE",
+        "third_party/brotli/LICENSE",
+        "third_party/icu/LICENSE",
+        "third_party/icu/scripts/LICENSE",
+        "third_party/libevent/LICENSE",
+        "third_party/metrics_proto/LICENSE",
+        "third_party/modp_b64/LICENSE",
+        "third_party/protobuf/LICENSE",
+        "third_party/protobuf/third_party/utf8_range/LICENSE",
+    ],
+}
+
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index e10c415..7829694 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -391,6 +391,9 @@
     self.processor_class = None
     self.sdk_version = None
     self.javacflags = set()
+    self.license_kinds = set()
+    self.license_text = set()
+    self.default_applicable_licenses = set()
 
   def to_string(self, output):
     if self.comment:
@@ -446,6 +449,9 @@
     self._output_field(output, 'processor_class')
     self._output_field(output, 'sdk_version')
     self._output_field(output, 'javacflags')
+    self._output_field(output, 'license_kinds')
+    self._output_field(output, 'license_text')
+    self._output_field(output, 'default_applicable_licenses')
     if self.rtti:
       self._output_field(output, 'rtti')
 
@@ -1647,6 +1653,59 @@
 
   return blueprint
 
+def create_license_module(blueprint):
+  module = Module("license", "external_cronet_license", "LICENSE")
+  module.license_kinds.update({
+      'SPDX-license-identifier-LGPL-2.1',
+      'SPDX-license-identifier-GPL-2.0',
+      'SPDX-license-identifier-MPL',
+      'SPDX-license-identifier-ISC',
+      'SPDX-license-identifier-GPL',
+      'SPDX-license-identifier-AFL-2.0',
+      'SPDX-license-identifier-MPL-2.0',
+      'SPDX-license-identifier-BSD',
+      'SPDX-license-identifier-Apache-2.0',
+      'SPDX-license-identifier-BSL-1.0',
+      'SPDX-license-identifier-LGPL',
+      'SPDX-license-identifier-GPL-3.0',
+      'SPDX-license-identifier-Unicode-DFS',
+      'SPDX-license-identifier-NCSA',
+      'SPDX-license-identifier-OpenSSL',
+      'SPDX-license-identifier-MIT',
+      "SPDX-license-identifier-ICU",
+      'legacy_unencumbered', # public domain
+  })
+  module.license_text.update({
+      "LICENSE",
+      "net/third_party/uri_template/LICENSE",
+      "net/third_party/quiche/src/LICENSE",
+      "base/third_party/symbolize/LICENSE",
+      "base/third_party/superfasthash/LICENSE",
+      "base/third_party/xdg_user_dirs/LICENSE",
+      "base/third_party/double_conversion/LICENSE",
+      "base/third_party/nspr/LICENSE",
+      "base/third_party/dynamic_annotations/LICENSE",
+      "base/third_party/icu/LICENSE",
+      "base/third_party/valgrind/LICENSE",
+      "third_party/brotli/LICENSE",
+      "third_party/protobuf/LICENSE",
+      "third_party/protobuf/third_party/utf8_range/LICENSE",
+      "third_party/metrics_proto/LICENSE",
+      "third_party/boringssl/src/LICENSE",
+      "third_party/boringssl/src/third_party/googletest/LICENSE",
+      "third_party/boringssl/src/third_party/wycheproof_testvectors/LICENSE",
+      "third_party/boringssl/src/third_party/fiat/LICENSE",
+      "third_party/libevent/LICENSE",
+      "third_party/ashmem/LICENSE",
+      "third_party/icu/LICENSE",
+      "third_party/icu/scripts/LICENSE",
+      "third_party/abseil-cpp/LICENSE",
+      "third_party/modp_b64/LICENSE",
+  })
+  default_license = Module("package", "", "PACKAGE")
+  default_license.default_applicable_licenses.add(module.name)
+  blueprint.add_module(module)
+  blueprint.add_module(default_license)
 
 def main():
   parser = argparse.ArgumentParser(
@@ -1698,7 +1757,7 @@
   # Add any proto groups to the blueprint.
   for l_name, t_names in proto_groups.items():
     create_proto_group_modules(blueprint, gn, l_name, t_names)
-
+  create_license_module(blueprint)
   output = [
       """// Copyright (C) 2022 The Android Open Source Project
 //