Merge "Fix flakeĀ : testVpnSetUnderlyingNetworks" into main
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index ffa2857..9520ef6 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -82,6 +82,17 @@
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
+// The filegroup lists files that are necessary for verifying building mdns as a standalone,
+// for use with service-connectivity-mdns-standalone-build-test
+filegroup {
+    name: "framework-connectivity-t-mdns-standalone-build-sources",
+    srcs: [
+        "src/android/net/nsd/OffloadEngine.java",
+        "src/android/net/nsd/OffloadServiceInfo.java",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
 java_library {
     name: "framework-connectivity-t-pre-jarjar",
     defaults: ["framework-connectivity-t-defaults"],
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 87b0a64..64762b4 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -348,3 +348,43 @@
 
 }
 
+package android.net.nsd {
+
+  public final class NsdManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine);
+  }
+
+  public interface OffloadEngine {
+    method public void onOffloadServiceRemoved(@NonNull android.net.nsd.OffloadServiceInfo);
+    method public void onOffloadServiceUpdated(@NonNull android.net.nsd.OffloadServiceInfo);
+    field public static final int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; // 0x1
+    field public static final int OFFLOAD_TYPE_FILTER_QUERIES = 2; // 0x2
+    field public static final int OFFLOAD_TYPE_FILTER_REPLIES = 4; // 0x4
+    field public static final int OFFLOAD_TYPE_REPLY = 1; // 0x1
+  }
+
+  public final class OffloadServiceInfo implements android.os.Parcelable {
+    ctor public OffloadServiceInfo(@NonNull android.net.nsd.OffloadServiceInfo.Key, @NonNull java.util.List<java.lang.String>, @NonNull String, @Nullable byte[], @IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int, long);
+    method public int describeContents();
+    method @NonNull public String getHostname();
+    method @NonNull public android.net.nsd.OffloadServiceInfo.Key getKey();
+    method @Nullable public byte[] getOffloadPayload();
+    method public long getOffloadType();
+    method public int getPriority();
+    method @NonNull public java.util.List<java.lang.String> getSubtypes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo> CREATOR;
+  }
+
+  public static final class OffloadServiceInfo.Key implements android.os.Parcelable {
+    ctor public OffloadServiceInfo.Key(@NonNull String, @NonNull String);
+    method public int describeContents();
+    method @NonNull public String getServiceName();
+    method @NonNull public String getServiceType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.OffloadServiceInfo.Key> CREATOR;
+  }
+
+}
+
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index 5533154..e671db1 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -17,6 +17,7 @@
 package android.net.nsd;
 
 import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.IOffloadEngine;
 import android.net.nsd.NsdServiceInfo;
 import android.os.Messenger;
 
@@ -35,4 +36,6 @@
     void stopResolution(int listenerKey);
     void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo);
     void unregisterServiceInfoCallback(int listenerKey);
+    void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType);
+    void unregisterOffloadEngine(in IOffloadEngine cb);
 }
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
new file mode 100644
index 0000000..2af733d
--- /dev/null
+++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl
@@ -0,0 +1,29 @@
+/**
+ * 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 android.net.nsd;
+
+import android.net.nsd.OffloadServiceInfo;
+
+/**
+ * Callbacks from NsdService to inform providers of packet offload.
+ *
+ * @hide
+ */
+oneway interface IOffloadEngine {
+    void onOffloadServiceUpdated(in OffloadServiceInfo info);
+    void onOffloadServiceRemoved(in OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 2930cbd..ef0e34b 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,9 @@
 
 package android.net.nsd;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
 import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
 
@@ -25,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.compat.CompatChanges;
 import android.content.Context;
@@ -45,9 +49,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -246,6 +252,10 @@
     public static final int UNREGISTER_SERVICE_CALLBACK             = 31;
     /** @hide */
     public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED   = 32;
+    /** @hide */
+    public static final int REGISTER_OFFLOAD_ENGINE                 = 33;
+    /** @hide */
+    public static final int UNREGISTER_OFFLOAD_ENGINE               = 34;
 
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -313,8 +323,107 @@
     private final ArrayMap<Integer, PerNetworkDiscoveryTracker>
             mPerNetworkDiscoveryMap = new ArrayMap<>();
 
+    @GuardedBy("mOffloadEngines")
+    private final ArrayList<OffloadEngineProxy> mOffloadEngines = new ArrayList<>();
     private final ServiceHandler mHandler;
 
+    private static class OffloadEngineProxy extends IOffloadEngine.Stub {
+        private final Executor mExecutor;
+        private final OffloadEngine mEngine;
+
+        private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) {
+            mExecutor = executor;
+            mEngine = appCb;
+        }
+
+        @Override
+        public void onOffloadServiceUpdated(OffloadServiceInfo info) {
+            mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info));
+        }
+
+        @Override
+        public void onOffloadServiceRemoved(OffloadServiceInfo info) {
+            mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info));
+        }
+    }
+
+    /**
+     * Registers an OffloadEngine with NsdManager.
+     *
+     * A caller can register itself as an OffloadEngine if it supports mDns hardware offload.
+     * The caller must implement the {@link OffloadEngine} interface and update hardware offload
+     * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and
+     * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be
+     * registered for the same interface, and that the same engine cannot be registered twice.
+     *
+     * @param ifaceName  indicates which network interface the hardware offload runs on
+     * @param offloadType    the type of offload that the offload engine support
+     * @param offloadCapability    the capabilities of the offload engine
+     * @param executor   the executor on which to receive the offload callbacks
+     * @param engine     the OffloadEngine that will receive the offload callbacks
+     * @throws IllegalStateException if the engine is already registered.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+            NETWORK_STACK})
+    public void registerOffloadEngine(@NonNull String ifaceName,
+            @OffloadEngine.OffloadType long offloadType,
+            @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor,
+            @NonNull OffloadEngine engine) {
+        Objects.requireNonNull(ifaceName);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(engine);
+        final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine);
+        synchronized (mOffloadEngines) {
+            if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) {
+                throw new IllegalStateException("This engine is already registered");
+            }
+            mOffloadEngines.add(cbImpl);
+        }
+        try {
+            mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Unregisters an OffloadEngine from NsdService.
+     *
+     * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the
+     * callback anymore. The OffloadEngine must have been previously registered with the system
+     * using the {@link NsdManager#registerOffloadEngine} method.
+     *
+     * @param engine OffloadEngine object to be removed from NsdService
+     * @throws IllegalStateException if the engine is not registered.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
+            NETWORK_STACK})
+    public void unregisterOffloadEngine(@NonNull OffloadEngine engine) {
+        Objects.requireNonNull(engine);
+        final OffloadEngineProxy cbImpl;
+        synchronized (mOffloadEngines) {
+            final int index = CollectionUtils.indexOf(mOffloadEngines,
+                    impl -> impl.mEngine == engine);
+            if (index < 0) {
+                throw new IllegalStateException("This engine is not registered");
+            }
+            cbImpl = mOffloadEngines.remove(index);
+        }
+
+        try {
+            mService.unregisterOffloadEngine(cbImpl);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private class PerNetworkDiscoveryTracker {
         final String mServiceType;
         final int mProtocolType;
diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java
new file mode 100644
index 0000000..b566b13
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadEngine.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.net.nsd;
+
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * OffloadEngine is an interface for mDns hardware offloading.
+ *
+ * An offloading engine can interact with the firmware code to instruct the hardware to
+ * offload some of mDns network traffic before it reached android OS. This can improve the
+ * power consumption performance of the host system by not always waking up the OS to handle
+ * the mDns packet when the device is in low power mode.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OffloadEngine {
+    /**
+     * Indicates that the OffloadEngine can generate replies to mDns queries.
+     *
+     * @see OffloadServiceInfo#getOffloadPayload()
+     */
+    int OFFLOAD_TYPE_REPLY = 1;
+    /**
+     * Indicates that the OffloadEngine can filter and drop mDns queries.
+     */
+    int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1;
+    /**
+     * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets
+     * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}.
+     */
+    int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2;
+
+    /**
+     * Indicates that the OffloadEngine can bypass multicast lock.
+     */
+    int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1;
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = {
+            OFFLOAD_TYPE_REPLY,
+            OFFLOAD_TYPE_FILTER_QUERIES,
+            OFFLOAD_TYPE_FILTER_REPLIES,
+    })
+    @interface OffloadType {}
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = {
+            OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK
+    })
+    @interface OffloadCapability {}
+
+    /**
+     * To be called when the OffloadServiceInfo is added or updated.
+     *
+     * @param info The OffloadServiceInfo to add or update.
+     */
+    void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info);
+
+    /**
+     * To be called when the OffloadServiceInfo is removed.
+     *
+     * @param info The OffloadServiceInfo to remove.
+     */
+    void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info);
+}
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
new file mode 100644
index 0000000..7bd5a7d
--- /dev/null
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -0,0 +1,314 @@
+/*
+ * 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 android.net.nsd;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.net.module.util.HexDump;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to
+ * know about how to offload an mDns service. The OffloadServiceInfo is keyed on
+ * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair.
+ *
+ * @hide
+ */
+@SystemApi
+public final class OffloadServiceInfo implements Parcelable {
+    @NonNull
+    private final Key mKey;
+    @NonNull
+    private final String mHostname;
+    @NonNull final List<String> mSubtypes;
+    @Nullable
+    private final byte[] mOffloadPayload;
+    private final int mPriority;
+    private final long mOffloadType;
+
+    /**
+     * Creates a new OffloadServiceInfo object with the specified parameters.
+     *
+     * @param key The key of the service.
+     * @param subtypes The list of subTypes of the service.
+     * @param hostname The name of the host offering the service. It is meaningful only when
+     *                 offloadType contains OFFLOAD_REPLY.
+     * @param offloadPayload The raw udp payload for hardware offloading.
+     * @param priority The priority of the service, @see #getPriority.
+     * @param offloadType The type of the service offload, @see #getOffloadType.
+     */
+    public OffloadServiceInfo(@NonNull Key key,
+            @NonNull List<String> subtypes, @NonNull String hostname,
+            @Nullable byte[] offloadPayload,
+            @IntRange(from = 0, to = Integer.MAX_VALUE) int priority,
+            @OffloadEngine.OffloadType long offloadType) {
+        Objects.requireNonNull(key);
+        Objects.requireNonNull(subtypes);
+        Objects.requireNonNull(hostname);
+        mKey = key;
+        mSubtypes = subtypes;
+        mHostname = hostname;
+        mOffloadPayload = offloadPayload;
+        mPriority = priority;
+        mOffloadType = offloadType;
+    }
+
+    /**
+     * Creates a new OffloadServiceInfo object from a Parcel.
+     *
+     * @param in The Parcel to read the object from.
+     *
+     * @hide
+     */
+    public OffloadServiceInfo(@NonNull Parcel in) {
+        mKey = in.readParcelable(Key.class.getClassLoader(),
+                Key.class);
+        mSubtypes = in.createStringArrayList();
+        mHostname = in.readString();
+        mOffloadPayload = in.createByteArray();
+        mPriority = in.readInt();
+        mOffloadType = in.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mKey, flags);
+        dest.writeStringList(mSubtypes);
+        dest.writeString(mHostname);
+        dest.writeByteArray(mOffloadPayload);
+        dest.writeInt(mPriority);
+        dest.writeLong(mOffloadType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<OffloadServiceInfo> CREATOR = new Creator<OffloadServiceInfo>() {
+        @Override
+        public OffloadServiceInfo createFromParcel(Parcel in) {
+            return new OffloadServiceInfo(in);
+        }
+
+        @Override
+        public OffloadServiceInfo[] newArray(int size) {
+            return new OffloadServiceInfo[size];
+        }
+    };
+
+    /**
+     * Get the {@link Key}.
+     */
+    @NonNull
+    public Key getKey() {
+        return mKey;
+    }
+
+    /**
+     * Get the host name. (e.g. "Android.local" )
+     */
+    @NonNull
+    public String getHostname() {
+        return mHostname;
+    }
+
+    /**
+     * Get the service subtypes. (e.g. ["_ann"] )
+     */
+    @NonNull
+    public List<String> getSubtypes() {
+        return Collections.unmodifiableList(mSubtypes);
+    }
+
+    /**
+     * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query.
+     * <p>
+     * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when
+     * replying to query.
+     */
+    @Nullable
+    public byte[] getOffloadPayload() {
+        if (mOffloadPayload == null) {
+            return null;
+        } else {
+            return mOffloadPayload.clone();
+        }
+    }
+
+    /**
+     * Get the offloadType.
+     * <p>
+     * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both
+     * filter the mDNS queries and replies, the {@link #mOffloadType} =
+     * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} |
+     * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}).
+     */
+    @OffloadEngine.OffloadType public long getOffloadType() {
+        return mOffloadType;
+    }
+
+    /**
+     * Get the priority for the OffloadServiceInfo.
+     * <p>
+     * When OffloadEngine don't have enough resource
+     * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo
+     * having lower priority values should be handled by the OffloadEngine first.
+     */
+    public int getPriority() {
+        return mPriority;
+    }
+
+    /**
+     * Only for debug purpose, the string can be long as the raw packet is dump in the string.
+     */
+    @Override
+    public String toString() {
+        return String.format(
+                "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, "
+                        + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }",
+                mKey,
+                mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority,
+                mOffloadType, mSubtypes.toString());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof OffloadServiceInfo)) return false;
+        OffloadServiceInfo that = (OffloadServiceInfo) o;
+        return mPriority == that.mPriority && mOffloadType == that.mOffloadType
+                && mKey.equals(that.mKey)
+                && mHostname.equals(
+                that.mHostname) && Arrays.equals(mOffloadPayload,
+                that.mOffloadPayload)
+                && mSubtypes.equals(that.mSubtypes);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mKey, mHostname, mPriority,
+                mOffloadType, mSubtypes);
+        result = 31 * result + Arrays.hashCode(mOffloadPayload);
+        return result;
+    }
+
+    /**
+     * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair.
+     */
+    public static final class Key implements Parcelable {
+        @NonNull
+        private final String mServiceName;
+        @NonNull
+        private final String mServiceType;
+
+        /**
+         * Creates a new OffloadServiceInfoKey object with the specified parameters.
+         *
+         * @param serviceName The name of the service.
+         * @param serviceType The type of the service.
+         */
+        public Key(@NonNull String serviceName, @NonNull String serviceType) {
+            Objects.requireNonNull(serviceName);
+            Objects.requireNonNull(serviceType);
+            mServiceName = serviceName;
+            mServiceType = serviceType;
+        }
+
+        /**
+         * Creates a new OffloadServiceInfoKey object from a Parcel.
+         *
+         * @param in The Parcel to read the object from.
+         *
+         * @hide
+         */
+        public Key(@NonNull Parcel in) {
+            mServiceName = in.readString();
+            mServiceType = in.readString();
+        }
+        /**
+         * Get the service name. (e.g. "NsdChat")
+         */
+        @NonNull
+        public String getServiceName() {
+            return mServiceName;
+        }
+
+        /**
+         * Get the service type. (e.g. "_http._tcp.local" )
+         */
+        @NonNull
+        public String getServiceType() {
+            return mServiceType;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString(mServiceName);
+            dest.writeString(mServiceType);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @NonNull
+        public static final Creator<Key> CREATOR =
+                new Creator<Key>() {
+            @Override
+            public Key createFromParcel(Parcel in) {
+                return new Key(in);
+            }
+
+            @Override
+            public Key[] newArray(int size) {
+                return new Key[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Key)) return false;
+            Key that = (Key) o;
+            return Objects.equals(mServiceName, that.mServiceName) && Objects.equals(
+                    mServiceType, that.mServiceType);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mServiceName, mServiceType);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }",
+                    mServiceName, mServiceType);
+        }
+    }
+}
diff --git a/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl
new file mode 100644
index 0000000..aa7aef2
--- /dev/null
+++ b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net.nsd;
+
+@JavaOnlyStableParcelable parcelable OffloadServiceInfo;
\ No newline at end of file
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
index 9b48d57..1ac5e8e 100644
--- a/framework/jarjar-excludes.txt
+++ b/framework/jarjar-excludes.txt
@@ -27,4 +27,10 @@
 # Don't touch anything that's already under android.net.http (cronet)
 # This is required since android.net.http contains api classes and hidden classes.
 # TODO: Remove this after hidden classes are moved to different package
-android\.net\.http\..+
\ No newline at end of file
+android\.net\.http\..+
+
+# TODO: OffloadServiceInfo is being added as an API, but wasn't an API yet in the first module
+# versions targeting U. Do not jarjar it such versions so that tests do not have to cover both
+# cases. This will be removed in an upcoming change marking it as API.
+android\.net\.nsd\.OffloadServiceInfo(\$.+)?
+android\.net\.nsd\.OffloadEngine(\$.+)?
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 7de749c..c277cf6 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -88,8 +88,9 @@
     name: "service-connectivity-mdns-standalone-build-test",
     sdk_version: "core_platform",
     srcs: [
-        ":service-mdns-droidstubs",
         "src/com/android/server/connectivity/mdns/**/*.java",
+        ":framework-connectivity-t-mdns-standalone-build-sources",
+        ":service-mdns-droidstubs"
     ],
     exclude_srcs: [
         "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index dab9d07..8d8dc32 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -34,6 +34,7 @@
 
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
+using android::bpf::bpfGetIfIndexStats;
 using android::bpf::NetworkTraceHandler;
 
 namespace android {
@@ -94,6 +95,15 @@
     }
 }
 
+static jlong nativeGetIfIndexStat(JNIEnv* env, jclass clazz, jint ifindex, jint type) {
+    Stats stats = {};
+    if (bpfGetIfIndexStats(ifindex, &stats) == 0) {
+        return getStatsType(&stats, (StatsType) type);
+    } else {
+        return UNKNOWN;
+    }
+}
+
 static jlong nativeGetUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
     Stats stats = {};
 
@@ -111,6 +121,7 @@
 static const JNINativeMethod gMethods[] = {
         {"nativeGetTotalStat", "(I)J", (void*)nativeGetTotalStat},
         {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)nativeGetIfaceStat},
+        {"nativeGetIfIndexStat", "(II)J", (void*)nativeGetIfIndexStat},
         {"nativeGetUidStat", "(II)J", (void*)nativeGetUidStat},
         {"nativeInitNetworkTracing", "()V", (void*)nativeInitNetworkTracing},
 };
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 1bc8ca5..2e6e3e5 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -40,8 +40,19 @@
 
 using base::Result;
 
+// This explicitly zero-initializes the relevant Stats fields.
+void InitStats(Stats* stats) {
+    stats->rxBytes = 0;
+    stats->rxPackets = 0;
+    stats->txBytes = 0;
+    stats->txPackets = 0;
+    stats->tcpRxPackets = -1;
+    stats->tcpTxPackets = -1;
+}
+
 int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
                            const BpfMap<uint32_t, StatsValue>& appUidStatsMap) {
+    InitStats(stats);
     auto statsEntry = appUidStatsMap.readValue(uid);
     if (statsEntry.ok()) {
         stats->rxPackets = statsEntry.value().rxPackets;
@@ -61,9 +72,8 @@
 int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
                              const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
                              const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
+    InitStats(stats);
     int64_t unknownIfaceBytesTotal = 0;
-    stats->tcpRxPackets = -1;
-    stats->tcpTxPackets = -1;
     const auto processIfaceStats =
             [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal](
                     const uint32_t& key,
@@ -95,6 +105,25 @@
     return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
 }
 
+int bpfGetIfIndexStatsInternal(uint32_t ifindex, Stats* stats,
+                               const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) {
+    InitStats(stats);
+    auto statsEntry = ifaceStatsMap.readValue(ifindex);
+    if (statsEntry.ok()) {
+        stats->rxPackets = statsEntry.value().rxPackets;
+        stats->txPackets = statsEntry.value().txPackets;
+        stats->rxBytes = statsEntry.value().rxBytes;
+        stats->txBytes = statsEntry.value().txBytes;
+        return 0;
+    }
+    return (statsEntry.error().code() == ENOENT) ? 0 : -statsEntry.error().code();
+}
+
+int bpfGetIfIndexStats(int ifindex, Stats* stats) {
+    static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
+    return bpfGetIfIndexStatsInternal(ifindex, stats, ifaceStatsMap);
+}
+
 stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
                               const char* ifname) {
     stats_line newLine;
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index ccd3f5e..4f85d9b 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -275,6 +275,20 @@
     expectStatsEqual(totalValue, totalResult);
 }
 
+TEST_F(BpfNetworkStatsHelperTest, TestGetIfIndexStatsInternal) {
+    StatsValue value = {
+          .rxPackets = TEST_PACKET0,
+          .rxBytes = TEST_BYTES0,
+          .txPackets = TEST_PACKET1,
+          .txBytes = TEST_BYTES1,
+    };
+    EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(IFACE_INDEX1, value, BPF_ANY));
+
+    Stats result = {};
+    ASSERT_EQ(0, bpfGetIfIndexStatsInternal(IFACE_INDEX1, &result, mFakeIfaceStatsMap));
+    expectStatsEqual(value, result);
+}
+
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsDetail) {
     updateIfaceMap(IFACE_NAME1, IFACE_INDEX1);
     updateIfaceMap(IFACE_NAME2, IFACE_INDEX2);
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 133009f..0a9c012 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -63,6 +63,9 @@
                              const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
                              const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
 // For test only
+int bpfGetIfIndexStatsInternal(uint32_t ifindex, Stats* stats,
+                               const BpfMap<uint32_t, StatsValue>& ifaceStatsMap);
+// For test only
 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
                                        const BpfMap<StatsKey, StatsValue>& statsMap,
                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap);
@@ -112,6 +115,7 @@
 
 int bpfGetUidStats(uid_t uid, Stats* stats);
 int bpfGetIfaceStats(const char* iface, Stats* stats);
+int bpfGetIfIndexStats(int ifindex, Stats* stats);
 int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines);
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 745c5bc..53782ac 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -46,9 +47,12 @@
 import android.net.nsd.INsdManager;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
+import android.net.nsd.IOffloadEngine;
 import android.net.nsd.MDnsManager;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.OffloadEngine;
+import android.net.nsd.OffloadServiceInfo;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Handler;
@@ -56,6 +60,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -98,6 +103,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -215,6 +221,24 @@
     // The number of client that ever connected.
     private int mClientNumberId = 1;
 
+    private final RemoteCallbackList<IOffloadEngine> mOffloadEngines =
+            new RemoteCallbackList<>();
+
+    private static class OffloadEngineInfo {
+        @NonNull final String mInterfaceName;
+        final long mOffloadCapabilities;
+        final long mOffloadType;
+        @NonNull final IOffloadEngine mOffloadEngine;
+
+        OffloadEngineInfo(@NonNull IOffloadEngine offloadEngine,
+                @NonNull String interfaceName, long capabilities, long offloadType) {
+            this.mOffloadEngine = offloadEngine;
+            this.mInterfaceName = interfaceName;
+            this.mOffloadCapabilities = capabilities;
+            this.mOffloadType = offloadType;
+        }
+    }
+
     private static class MdnsListener implements MdnsServiceBrowserListener {
         protected final int mClientRequestId;
         protected final int mTransactionId;
@@ -719,6 +743,7 @@
                 final int transactionId;
                 final int clientRequestId = msg.arg2;
                 final ListenerArgs args;
+                final OffloadEngineInfo offloadEngineInfo;
                 switch (msg.what) {
                     case NsdManager.DISCOVER_SERVICES: {
                         if (DBG) Log.d(TAG, "Discover services");
@@ -1114,6 +1139,16 @@
                             return NOT_HANDLED;
                         }
                         break;
+                    case NsdManager.REGISTER_OFFLOAD_ENGINE:
+                        offloadEngineInfo = (OffloadEngineInfo) msg.obj;
+                        // TODO: Limits the number of registrations created by a given class.
+                        mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
+                                offloadEngineInfo);
+                        // TODO: Sends all the existing OffloadServiceInfos back.
+                        break;
+                    case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
+                        mOffloadEngines.unregister((IOffloadEngine) msg.obj);
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -1573,7 +1608,8 @@
                 mRunningAppActiveImportanceCutoff);
 
         mMdnsSocketClient =
-                new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
+                new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
+                        LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
         mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
                 mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
         handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
@@ -1771,7 +1807,42 @@
         }
     }
 
+    private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
+            @NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
+        final int count = mOffloadEngines.beginBroadcast();
+        try {
+            for (int i = 0; i < count; i++) {
+                final OffloadEngineInfo offloadEngineInfo =
+                        (OffloadEngineInfo) mOffloadEngines.getBroadcastCookie(i);
+                final String interfaceName = offloadEngineInfo.mInterfaceName;
+                if (!targetInterfaceName.equals(interfaceName)
+                        || ((offloadEngineInfo.mOffloadType
+                        & offloadServiceInfo.getOffloadType()) == 0)) {
+                    continue;
+                }
+                try {
+                    if (isRemove) {
+                        mOffloadEngines.getBroadcastItem(i).onOffloadServiceRemoved(
+                                offloadServiceInfo);
+                    } else {
+                        mOffloadEngines.getBroadcastItem(i).onOffloadServiceUpdated(
+                                offloadServiceInfo);
+                    }
+                } catch (RemoteException e) {
+                    // Can happen in regular cases, do not log a stacktrace
+                    Log.i(TAG, "Failed to send offload callback, remote died", e);
+                }
+            }
+        } finally {
+            mOffloadEngines.finishBroadcast();
+        }
+    }
+
     private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback {
+        // TODO: add a callback to notify when a service is being added on each interface (as soon
+        // as probing starts), and call mOffloadCallbacks. This callback is for
+        // OFFLOAD_CAPABILITY_FILTER_REPLIES offload type.
+
         @Override
         public void onRegisterServiceSucceeded(int transactionId, NsdServiceInfo registeredInfo) {
             mServiceLogs.log("onRegisterServiceSucceeded: transactionId " + transactionId);
@@ -1801,6 +1872,18 @@
                     request.calculateRequestDurationMs());
         }
 
+        @Override
+        public void onOffloadStartOrUpdate(@NonNull String interfaceName,
+                @NonNull OffloadServiceInfo offloadServiceInfo) {
+            sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, false /* isRemove */);
+        }
+
+        @Override
+        public void onOffloadStop(@NonNull String interfaceName,
+                @NonNull OffloadServiceInfo offloadServiceInfo) {
+            sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, true /* isRemove */);
+        }
+
         private ClientInfo getClientInfoOrLog(int transactionId) {
             final ClientInfo clientInfo = mTransactionIdToClientInfoMap.get(transactionId);
             if (clientInfo == null) {
@@ -1920,6 +2003,32 @@
         public void binderDied() {
             mNsdStateMachine.sendMessage(
                     mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this));
+
+        }
+
+        @Override
+        public void registerOffloadEngine(String ifaceName, IOffloadEngine cb,
+                @OffloadEngine.OffloadCapability long offloadCapabilities,
+                @OffloadEngine.OffloadType long offloadTypes) {
+            // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
+            //  it may not be possible for all the callers of this API to have it.
+            PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+            Objects.requireNonNull(ifaceName);
+            Objects.requireNonNull(cb);
+            mNsdStateMachine.sendMessage(
+                    mNsdStateMachine.obtainMessage(NsdManager.REGISTER_OFFLOAD_ENGINE,
+                            new OffloadEngineInfo(cb, ifaceName, offloadCapabilities,
+                                    offloadTypes)));
+        }
+
+        @Override
+        public void unregisterOffloadEngine(IOffloadEngine cb) {
+            // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and
+            //  it may not be possible for all the callers of this API to have it.
+            PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+            Objects.requireNonNull(cb);
+            mNsdStateMachine.sendMessage(
+                    mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_OFFLOAD_ENGINE, cb));
         }
     }
 
@@ -2003,25 +2112,41 @@
             return IFACE_IDX_ANY;
         }
 
-        final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
-        if (cm == null) {
-            Log.wtf(TAG, "No ConnectivityManager for resolveService");
+        String interfaceName = getNetworkInterfaceName(network);
+        if (interfaceName == null) {
             return IFACE_IDX_ANY;
         }
-        final LinkProperties lp = cm.getLinkProperties(network);
-        if (lp == null) return IFACE_IDX_ANY;
+        return getNetworkInterfaceIndexByName(interfaceName);
+    }
 
+    private String getNetworkInterfaceName(@Nullable Network network) {
+        if (network == null) {
+            return null;
+        }
+        final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+        if (cm == null) {
+            Log.wtf(TAG, "No ConnectivityManager");
+            return null;
+        }
+        final LinkProperties lp = cm.getLinkProperties(network);
+        if (lp == null) {
+            return null;
+        }
         // Only resolve on non-stacked interfaces
+        return lp.getInterfaceName();
+    }
+
+    private int getNetworkInterfaceIndexByName(final String ifaceName) {
         final NetworkInterface iface;
         try {
-            iface = NetworkInterface.getByName(lp.getInterfaceName());
+            iface = NetworkInterface.getByName(ifaceName);
         } catch (SocketException e) {
             Log.e(TAG, "Error querying interface", e);
             return IFACE_IDX_ANY;
         }
 
         if (iface == null) {
-            Log.e(TAG, "Interface not found: " + lp.getInterfaceName());
+            Log.e(TAG, "Interface not found: " + ifaceName);
             return IFACE_IDX_ANY;
         }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
index 551e3db..87aa0d2 100644
--- a/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
+++ b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
@@ -25,13 +25,12 @@
 import android.net.NetworkRequest;
 import android.os.Build;
 
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
 
 /** Class for monitoring connectivity changes using {@link ConnectivityManager}. */
 public class ConnectivityMonitorWithConnectivityManager implements ConnectivityMonitor {
     private static final String TAG = "ConnMntrWConnMgr";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
-
+    private final SharedLog sharedLog;
     private final Listener listener;
     private final ConnectivityManager.NetworkCallback networkCallback;
     private final ConnectivityManager connectivityManager;
@@ -42,8 +41,10 @@
 
     @SuppressWarnings({"nullness:assignment", "nullness:method.invocation"})
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public ConnectivityMonitorWithConnectivityManager(Context context, Listener listener) {
+    public ConnectivityMonitorWithConnectivityManager(Context context, Listener listener,
+            SharedLog sharedLog) {
         this.listener = listener;
+        this.sharedLog = sharedLog;
 
         connectivityManager =
                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -51,20 +52,20 @@
                 new ConnectivityManager.NetworkCallback() {
                     @Override
                     public void onAvailable(Network network) {
-                        LOGGER.log("network available.");
+                        sharedLog.log("network available.");
                         lastAvailableNetwork = network;
                         notifyConnectivityChange();
                     }
 
                     @Override
                     public void onLost(Network network) {
-                        LOGGER.log("network lost.");
+                        sharedLog.log("network lost.");
                         notifyConnectivityChange();
                     }
 
                     @Override
                     public void onUnavailable() {
-                        LOGGER.log("network unavailable.");
+                        sharedLog.log("network unavailable.");
                         notifyConnectivityChange();
                     }
                 };
@@ -82,7 +83,7 @@
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     @Override
     public void startWatchingConnectivityChanges() {
-        LOGGER.log("Start watching connectivity changes");
+        sharedLog.log("Start watching connectivity changes");
         if (isCallbackRegistered) {
             return;
         }
@@ -98,7 +99,7 @@
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
     @Override
     public void stopWatchingConnectivityChanges() {
-        LOGGER.log("Stop watching connectivity changes");
+        sharedLog.log("Stop watching connectivity changes");
         if (!isCallbackRegistered) {
             return;
         }
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index b7417ed..fa3b646 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -20,10 +20,9 @@
 
 import android.annotation.NonNull;
 import android.text.TextUtils;
-import android.util.Log;
 import android.util.Pair;
 
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
@@ -44,7 +43,6 @@
 public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
 
     private static final String TAG = "MdnsQueryCallable";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
     private static final List<Integer> castShellEmulatorMdnsPorts;
 
     static {
@@ -77,6 +75,8 @@
     private final List<MdnsResponse> servicesToResolve;
     @NonNull
     private final MdnsUtils.Clock clock;
+    @NonNull
+    private final SharedLog sharedLog;
     private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
 
     EnqueueMdnsQueryCallable(
@@ -90,7 +90,8 @@
             boolean onlyUseIpv6OnIpv6OnlyNetworks,
             boolean sendDiscoveryQueries,
             @NonNull Collection<MdnsResponse> servicesToResolve,
-            @NonNull MdnsUtils.Clock clock) {
+            @NonNull MdnsUtils.Clock clock,
+            @NonNull SharedLog sharedLog) {
         weakRequestSender = new WeakReference<>(requestSender);
         this.packetWriter = packetWriter;
         serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -102,6 +103,7 @@
         this.sendDiscoveryQueries = sendDiscoveryQueries;
         this.servicesToResolve = new ArrayList<>(servicesToResolve);
         this.clock = clock;
+        this.sharedLog = sharedLog;
     }
 
     /**
@@ -200,7 +202,7 @@
             }
             return Pair.create(transactionId, subtypes);
         } catch (IOException e) {
-            LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
+            sharedLog.e(String.format("Failed to create mDNS packet for subtype: %s.",
                     TextUtils.join(",", subtypes)), e);
             return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
         }
@@ -242,13 +244,13 @@
             sendPacket(requestSender,
                     new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port));
         } catch (IOException e) {
-            Log.i(TAG, "Can't send packet to IPv4", e);
+            sharedLog.e("Can't send packet to IPv4", e);
         }
         try {
             sendPacket(requestSender,
                     new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port));
         } catch (IOException e) {
-            Log.i(TAG, "Can't send packet to IPv6", e);
+            sharedLog.e("Can't send packet to IPv6", e);
         }
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 158d7a3..dd72d11 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -24,15 +24,19 @@
 import android.net.Network;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.OffloadEngine;
+import android.net.nsd.OffloadServiceInfo;
 import android.os.Looper;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
@@ -68,9 +72,10 @@
             new ArrayMap<>();
     private final SparseArray<Registration> mRegistrations = new SparseArray<>();
     private final Dependencies mDeps;
-
     private String[] mDeviceHostName;
     @NonNull private final SharedLog mSharedLog;
+    private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
+            new ArrayMap<>();
 
     /**
      * Dependencies for {@link MdnsAdvertiser}, useful for testing.
@@ -115,18 +120,32 @@
     private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
             new MdnsInterfaceAdvertiser.Callback() {
         @Override
-        public void onRegisterServiceSucceeded(
+        public void onServiceProbingSucceeded(
                 @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+            final Registration registration = mRegistrations.get(serviceId);
+            if (registration == null) {
+                mSharedLog.wtf("Register succeeded for unknown registration");
+                return;
+            }
+
+            final String interfaceName = advertiser.getSocketInterfaceName();
+            final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+                    mInterfaceOffloadServices.computeIfAbsent(
+                            interfaceName, k -> new ArrayList<>());
+            // Remove existing offload services from cache for update.
+            existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId);
+            final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService(
+                    serviceId,
+                    registration);
+            existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
+            mCb.onOffloadStartOrUpdate(interfaceName,
+                    newOffloadServiceInfoWrapper.mOffloadServiceInfo);
+
             // Wait for all current interfaces to be done probing before notifying of success.
             if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return;
             // The service may still be unregistered/renamed if a conflict is found on a later added
             // interface, or if a conflicting announcement/reply is detected (RFC6762 9.)
 
-            final Registration registration = mRegistrations.get(serviceId);
-            if (registration == null) {
-                Log.wtf(TAG, "Register succeeded for unknown registration");
-                return;
-            }
             if (!registration.mNotifiedRegistrationSuccess) {
                 mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo());
                 registration.mNotifiedRegistrationSuccess = true;
@@ -148,7 +167,12 @@
                 registration.mNotifiedRegistrationSuccess = false;
 
                 // The service was done probing, just reset it to probing state (RFC6762 9.)
-                forAllAdvertisers(a -> a.restartProbingForConflict(serviceId));
+                forAllAdvertisers(a -> {
+                    if (!a.maybeRestartProbingForConflict(serviceId)) {
+                        return;
+                    }
+                    maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId);
+                });
                 return;
             }
 
@@ -196,6 +220,22 @@
         registration.updateForConflict(newInfo, renameCount);
     }
 
+    private void maybeSendOffloadStop(final String interfaceName, int serviceId) {
+        final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
+                mInterfaceOffloadServices.get(interfaceName);
+        if (existingOffloadServiceInfoWrappers == null) {
+            return;
+        }
+        // Stop the offloaded service by matching the service id
+        int idx = CollectionUtils.indexOf(existingOffloadServiceInfoWrappers,
+                item -> item.mServiceId == serviceId);
+        if (idx >= 0) {
+            mCb.onOffloadStop(interfaceName,
+                    existingOffloadServiceInfoWrappers.get(idx).mOffloadServiceInfo);
+            existingOffloadServiceInfoWrappers.remove(idx);
+        }
+    }
+
     /**
      * A request for a {@link MdnsInterfaceAdvertiser}.
      *
@@ -221,7 +261,22 @@
          * @return true if this {@link InterfaceAdvertiserRequest} should now be deleted.
          */
         boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
-            mAdvertisers.remove(socket);
+            final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket);
+            if (removedAdvertiser != null) {
+                final String interfaceName = removedAdvertiser.getSocketInterfaceName();
+                // If the interface is destroyed, stop all hardware offloading on that interface.
+                final List<OffloadServiceInfoWrapper> offloadServiceInfoWrappers =
+                        mInterfaceOffloadServices.remove(
+                                interfaceName);
+                if (offloadServiceInfoWrappers != null) {
+                    for (OffloadServiceInfoWrapper offloadServiceInfoWrapper :
+                            offloadServiceInfoWrappers) {
+                        mCb.onOffloadStop(interfaceName,
+                                offloadServiceInfoWrapper.mOffloadServiceInfo);
+                    }
+                }
+            }
+
             if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) {
                 // No advertiser is using sockets from this request anymore (in particular for exit
                 // announcements), and there is no registration so newer sockets will not be
@@ -274,7 +329,8 @@
                     mAdvertisers.valueAt(i).addService(
                             id, registration.getServiceInfo(), registration.getSubtype());
                 } catch (NameConflictException e) {
-                    Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+                    mSharedLog.wtf("Name conflict adding services that should have unique names",
+                            e);
                 }
             }
         }
@@ -282,7 +338,10 @@
         void removeService(int id) {
             mPendingRegistrations.remove(id);
             for (int i = 0; i < mAdvertisers.size(); i++) {
-                mAdvertisers.valueAt(i).removeService(id);
+                final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i);
+                advertiser.removeService(id);
+
+                maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id);
             }
         }
 
@@ -305,7 +364,8 @@
                     advertiser.addService(mPendingRegistrations.keyAt(i),
                             registration.getServiceInfo(), registration.getSubtype());
                 } catch (NameConflictException e) {
-                    Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+                    mSharedLog.wtf("Name conflict adding services that should have unique names",
+                            e);
                 }
             }
         }
@@ -325,6 +385,16 @@
         }
     }
 
+    private static class OffloadServiceInfoWrapper {
+        private final @NonNull OffloadServiceInfo mOffloadServiceInfo;
+        private final int mServiceId;
+
+        OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
+            mOffloadServiceInfo = offloadServiceInfo;
+            mServiceId = serviceId;
+        }
+    }
+
     private static class Registration {
         @NonNull
         final String mOriginalName;
@@ -425,6 +495,24 @@
 
         // Unregistration is notified immediately as success in NsdService so no callback is needed
         // here.
+
+        /**
+         * Called when a service is ready to be sent for hardware offloading.
+         *
+         * @param interfaceName the interface for sending the update to.
+         * @param offloadServiceInfo the offloading content.
+         */
+        void onOffloadStartOrUpdate(@NonNull String interfaceName,
+                @NonNull OffloadServiceInfo offloadServiceInfo);
+
+        /**
+         * Called when a service is removed or the MdnsInterfaceAdvertiser is destroyed.
+         *
+         * @param interfaceName the interface for sending the update to.
+         * @param offloadServiceInfo the offloading content.
+         */
+        void onOffloadStop(@NonNull String interfaceName,
+                @NonNull OffloadServiceInfo offloadServiceInfo);
     }
 
     public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
@@ -459,7 +547,7 @@
     public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
         checkThread();
         if (mRegistrations.get(id) != null) {
-            Log.e(TAG, "Adding duplicate registration for " + service);
+            mSharedLog.e("Adding duplicate registration for " + service);
             // TODO (b/264986328): add a more specific error code
             mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
             return;
@@ -525,4 +613,28 @@
             return false;
         });
     }
+
+    private OffloadServiceInfoWrapper createOffloadService(int serviceId,
+            @NonNull Registration registration) {
+        final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
+        List<String> subTypes = new ArrayList<>();
+        String subType = registration.getSubtype();
+        if (subType != null) {
+            subTypes.add(subType);
+        }
+        final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
+                new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
+                        nsdServiceInfo.getServiceType()),
+                subTypes,
+                String.join(".", mDeviceHostName),
+                null /* rawOffloadPacket */,
+                // TODO: define overlayable resources in
+                // ServiceConnectivityResources that set the priority based on
+                // service type.
+                0 /* priority */,
+                // TODO: set the offloadType based on the callback timing.
+                OffloadEngine.OFFLOAD_TYPE_REPLY);
+        return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
+    }
+
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
index 27fc945..fd2c32e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
@@ -21,6 +21,7 @@
 import android.os.Looper;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
 
 import java.util.Collections;
 import java.util.List;
@@ -39,9 +40,6 @@
     private static final long EXIT_DELAY_MS = 2000L;
     private static final int EXIT_COUNT = 3;
 
-    @NonNull
-    private final String mLogTag;
-
     /** Base class for announcement requests to send with {@link MdnsAnnouncer}. */
     public abstract static class BaseAnnouncementInfo implements MdnsPacketRepeater.Request {
         private final int mServiceId;
@@ -105,16 +103,11 @@
         }
     }
 
-    public MdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
+    public MdnsAnnouncer(@NonNull Looper looper,
             @NonNull MdnsReplySender replySender,
-            @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb) {
-        super(looper, replySender, cb);
-        mLogTag = MdnsAnnouncer.class.getSimpleName() + "/" + interfaceTag;
-    }
-
-    @Override
-    protected String getTag() {
-        return mLogTag;
+            @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb,
+            @NonNull SharedLog sharedLog) {
+        super(looper, replySender, cb, sharedLog);
     }
 
     // TODO: Notify MdnsRecordRepository that the records were announced for that service ID,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 724a704..a83b852 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -22,7 +22,6 @@
 import android.net.nsd.NsdServiceInfo;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.HexDump;
@@ -42,8 +41,6 @@
     @VisibleForTesting
     public static final long EXIT_ANNOUNCEMENT_DELAY_MS = 100L;
     @NonNull
-    private final String mTag;
-    @NonNull
     private final ProbingCallback mProbingCallback = new ProbingCallback();
     @NonNull
     private final AnnouncingCallback mAnnouncingCallback = new AnnouncingCallback();
@@ -73,7 +70,7 @@
         /**
          * Called by the advertiser after it successfully registered a service, after probing.
          */
-        void onRegisterServiceSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+        void onServiceProbingSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
 
         /**
          * Called by the advertiser when a conflict was found, during or after probing.
@@ -101,7 +98,7 @@
         public void onFinished(MdnsProber.ProbingInfo info) {
             final MdnsAnnouncer.AnnouncementInfo announcementInfo;
             mSharedLog.i("Probing finished for service " + info.getServiceId());
-            mCbHandler.post(() -> mCb.onRegisterServiceSucceeded(
+            mCbHandler.post(() -> mCb.onServiceProbingSucceeded(
                     MdnsInterfaceAdvertiser.this, info.getServiceId()));
             try {
                 announcementInfo = mRecordRepository.onProbingSucceeded(info);
@@ -151,22 +148,30 @@
         /** @see MdnsReplySender */
         @NonNull
         public MdnsReplySender makeReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
-                @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
-            return new MdnsReplySender(interfaceTag, looper, socket, packetCreationBuffer);
+                @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer,
+                @NonNull SharedLog sharedLog) {
+            return new MdnsReplySender(looper, socket, packetCreationBuffer,
+                    sharedLog.forSubComponent(
+                            MdnsReplySender.class.getSimpleName() + "/" + interfaceTag));
         }
 
         /** @see MdnsAnnouncer */
         public MdnsAnnouncer makeMdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
                 @NonNull MdnsReplySender replySender,
-                @Nullable PacketRepeaterCallback<MdnsAnnouncer.BaseAnnouncementInfo> cb) {
-            return new MdnsAnnouncer(interfaceTag, looper, replySender, cb);
+                @Nullable PacketRepeaterCallback<MdnsAnnouncer.BaseAnnouncementInfo> cb,
+                @NonNull SharedLog sharedLog) {
+            return new MdnsAnnouncer(looper, replySender, cb,
+                    sharedLog.forSubComponent(
+                            MdnsAnnouncer.class.getSimpleName() + "/" + interfaceTag));
         }
 
         /** @see MdnsProber */
         public MdnsProber makeMdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
                 @NonNull MdnsReplySender replySender,
-                @NonNull PacketRepeaterCallback<MdnsProber.ProbingInfo> cb) {
-            return new MdnsProber(interfaceTag, looper, replySender, cb);
+                @NonNull PacketRepeaterCallback<MdnsProber.ProbingInfo> cb,
+                @NonNull SharedLog sharedLog) {
+            return new MdnsProber(looper, replySender, cb, sharedLog.forSubComponent(
+                    MdnsProber.class.getSimpleName() + "/" + interfaceTag));
         }
     }
 
@@ -182,17 +187,17 @@
             @NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
             @NonNull byte[] packetCreationBuffer, @NonNull Callback cb, @NonNull Dependencies deps,
             @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
-        mTag = MdnsInterfaceAdvertiser.class.getSimpleName() + "/" + sharedLog.getTag();
         mRecordRepository = deps.makeRecordRepository(looper, deviceHostName);
         mRecordRepository.updateAddresses(initialAddresses);
         mSocket = socket;
         mCb = cb;
         mCbHandler = new Handler(looper);
         mReplySender = deps.makeReplySender(sharedLog.getTag(), looper, socket,
-                packetCreationBuffer);
+                packetCreationBuffer, sharedLog);
         mAnnouncer = deps.makeMdnsAnnouncer(sharedLog.getTag(), looper, mReplySender,
-                mAnnouncingCallback);
-        mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback);
+                mAnnouncingCallback, sharedLog);
+        mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback,
+                sharedLog);
         mSharedLog = sharedLog;
     }
 
@@ -282,11 +287,12 @@
     /**
      * Reset a service to the probing state due to a conflict found on the network.
      */
-    public void restartProbingForConflict(int serviceId) {
+    public boolean maybeRestartProbingForConflict(int serviceId) {
         final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId);
-        if (probingInfo == null) return;
+        if (probingInfo == null) return false;
 
         mProber.restartForConflict(probingInfo);
+        return true;
     }
 
     /**
@@ -317,20 +323,18 @@
         try {
             packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length));
         } catch (MdnsPacket.ParseException e) {
-            Log.e(mTag, "Error parsing mDNS packet", e);
+            mSharedLog.e("Error parsing mDNS packet", e);
             if (DBG) {
-                Log.v(
-                        mTag, "Packet: " + HexDump.toHexString(recvbuf, 0, length));
+                mSharedLog.v("Packet: " + HexDump.toHexString(recvbuf, 0, length));
             }
             return;
         }
 
         if (DBG) {
-            Log.v(mTag,
-                    "Parsed packet with " + packet.questions.size() + " questions, "
-                            + packet.answers.size() + " answers, "
-                            + packet.authorityRecords.size() + " authority, "
-                            + packet.additionalRecords.size() + " additional from " + src);
+            mSharedLog.v("Parsed packet with " + packet.questions.size() + " questions, "
+                    + packet.answers.size() + " answers, "
+                    + packet.authorityRecords.size() + " authority, "
+                    + packet.additionalRecords.size() + " additional from " + src);
         }
 
         for (int conflictServiceId : mRecordRepository.getConflictingServices(packet)) {
@@ -346,4 +350,8 @@
         if (answers == null) return;
         mReplySender.queueReply(answers);
     }
+
+    public String getSocketInterfaceName() {
+        return mSocket.getInterface().getName();
+    }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 119c7a8..534f8d0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -28,7 +28,8 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -54,11 +55,12 @@
     @NonNull private final NetworkInterface mNetworkInterface;
     @NonNull private final MulticastPacketReader mPacketReader;
     @NonNull private final ParcelFileDescriptor mFileDescriptor;
+    @NonNull private final SharedLog mSharedLog;
     private boolean mJoinedIpv4 = false;
     private boolean mJoinedIpv6 = false;
 
     public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port,
-            @NonNull Looper looper, @NonNull byte[] packetReadBuffer)
+            @NonNull Looper looper, @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog)
             throws IOException {
         mNetworkInterface = networkInterface;
         mMulticastSocket = new MulticastSocket(port);
@@ -80,6 +82,8 @@
         mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
                 new Handler(looper), packetReadBuffer);
         mPacketReader.start();
+
+        mSharedLog = sharedLog;
     }
 
     /**
@@ -117,7 +121,7 @@
             return true;
         } catch (IOException e) {
             // The address may have just been removed
-            Log.e(TAG, "Error joining multicast group for " + mNetworkInterface, e);
+            mSharedLog.e("Error joining multicast group for " + mNetworkInterface, e);
             return false;
         }
     }
@@ -148,7 +152,7 @@
         try {
             mFileDescriptor.close();
         } catch (IOException e) {
-            Log.e(TAG, "Close file descriptor failed.");
+            mSharedLog.e("Close file descriptor failed.");
         }
         mMulticastSocket.close();
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index d1fa57c..097dbe0 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -25,7 +25,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -46,6 +47,7 @@
 
     @NonNull private final Handler mHandler;
     @NonNull private final MdnsSocketProvider mSocketProvider;
+    @NonNull private final SharedLog mSharedLog;
 
     private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
             new ArrayMap<>();
@@ -55,9 +57,11 @@
     private int mReceivedPacketNumber = 0;
 
     public MdnsMultinetworkSocketClient(@NonNull Looper looper,
-            @NonNull MdnsSocketProvider provider) {
+            @NonNull MdnsSocketProvider provider,
+            @NonNull SharedLog sharedLog) {
         mHandler = new Handler(looper);
         mSocketProvider = provider;
+        mSharedLog = sharedLog;
     }
 
     private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -172,7 +176,7 @@
             throw new IllegalArgumentException("Can not register duplicated listener");
         }
 
-        if (DBG) Log.d(TAG, "notifyNetworkRequested: network=" + network);
+        if (DBG) mSharedLog.v("notifyNetworkRequested: network=" + network);
         callback = new InterfaceSocketCallback(socketCreationCallback);
         mRequestedNetworks.put(listener, callback);
         mSocketProvider.requestSocket(network, callback);
@@ -184,7 +188,7 @@
         ensureRunningOnHandlerThread(mHandler);
         final InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
         if (callback == null) {
-            Log.e(TAG, "Can not be unrequested with unknown listener=" + listener);
+            mSharedLog.e("Can not be unrequested with unknown listener=" + listener);
             return;
         }
         callback.onNetworkUnrequested();
@@ -222,7 +226,7 @@
                 try {
                     socket.send(packet);
                 } catch (IOException e) {
-                    Log.e(TAG, "Failed to send a mDNS packet.", e);
+                    mSharedLog.e("Failed to send a mDNS packet.", e);
                 }
             }
         }
@@ -249,7 +253,7 @@
             response = MdnsResponseDecoder.parseResponse(recvbuf, length);
         } catch (MdnsPacket.ParseException e) {
             if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
-                Log.e(TAG, e.getMessage(), e);
+                mSharedLog.e(e.getMessage(), e);
                 if (mCallback != null) {
                     mCallback.onFailedToParseMdnsResponse(packetNumber, e.code, socketKey);
                 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index 4c385da..644560c 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -24,7 +24,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
@@ -45,6 +46,8 @@
     protected final Handler mHandler;
     @Nullable
     private final PacketRepeaterCallback<T> mCb;
+    @NonNull
+    private final SharedLog mSharedLog;
 
     /**
      * Status callback from {@link MdnsPacketRepeater}.
@@ -87,12 +90,6 @@
         int getNumSends();
     }
 
-    /**
-     * Get the logging tag to use.
-     */
-    @NonNull
-    protected abstract String getTag();
-
     private final class ProbeHandler extends Handler {
         ProbeHandler(@NonNull Looper looper) {
             super(looper);
@@ -112,7 +109,7 @@
 
             final MdnsPacket packet = request.getPacket(index);
             if (DBG) {
-                Log.v(getTag(), "Sending packets for iteration " + index + " out of "
+                mSharedLog.v("Sending packets for iteration " + index + " out of "
                         + request.getNumSends() + " for ID " + msg.what);
             }
             // Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
@@ -121,7 +118,7 @@
                 try {
                     mReplySender.sendNow(packet, destination);
                 } catch (IOException e) {
-                    Log.e(getTag(), "Error sending packet to " + destination, e);
+                    mSharedLog.e("Error sending packet to " + destination, e);
                 }
             }
 
@@ -133,7 +130,7 @@
                 // likely not to be available since the device is in deep sleep anyway.
                 final long delay = request.getDelayMs(nextIndex);
                 sendMessageDelayed(obtainMessage(msg.what, nextIndex, 0, request), delay);
-                if (DBG) Log.v(getTag(), "Scheduled next packet in " + delay + "ms");
+                if (DBG) mSharedLog.v("Scheduled next packet in " + delay + "ms");
             }
 
             // Call onSent after scheduling the next run, to allow the callback to cancel it
@@ -144,15 +141,16 @@
     }
 
     protected MdnsPacketRepeater(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
-            @Nullable PacketRepeaterCallback<T> cb) {
+            @Nullable PacketRepeaterCallback<T> cb, @NonNull SharedLog sharedLog) {
         mHandler = new ProbeHandler(looper);
         mReplySender = replySender;
         mCb = cb;
+        mSharedLog = sharedLog;
     }
 
     protected void startSending(int id, @NonNull T request, long initialDelayMs) {
         if (DBG) {
-            Log.v(getTag(), "Starting send with id " + id + ", request "
+            mSharedLog.v("Starting send with id " + id + ", request "
                     + request.getClass().getSimpleName() + ", delay " + initialDelayMs);
         }
         mHandler.sendMessageDelayed(mHandler.obtainMessage(id, 0, 0, request), initialDelayMs);
@@ -171,7 +169,7 @@
         // message cannot be cancelled.
         if (mHandler.hasMessages(id)) {
             if (DBG) {
-                Log.v(getTag(), "Stopping send on id " + id);
+                mSharedLog.v("Stopping send on id " + id);
             }
             mHandler.removeMessages(id);
             return true;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
index ecf846e..ba37f32 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -21,6 +21,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.util.ArrayList;
@@ -34,14 +35,11 @@
  */
 public class MdnsProber extends MdnsPacketRepeater<MdnsProber.ProbingInfo> {
     private static final long CONFLICT_RETRY_DELAY_MS = 5_000L;
-    @NonNull
-    private final String mLogTag;
 
-    public MdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
-            @NonNull MdnsReplySender replySender,
-            @NonNull PacketRepeaterCallback<ProbingInfo> cb) {
-        super(looper, replySender, cb);
-        mLogTag = MdnsProber.class.getSimpleName() + "/" + interfaceTag;
+    public MdnsProber(@NonNull Looper looper, @NonNull MdnsReplySender replySender,
+            @NonNull PacketRepeaterCallback<ProbingInfo> cb,
+            @NonNull SharedLog sharedLog) {
+        super(looper, replySender, cb, sharedLog);
     }
 
     /** Probing request to send with {@link MdnsProber}. */
@@ -118,11 +116,6 @@
         }
     }
 
-    @NonNull
-    @Override
-    protected String getTag() {
-        return mLogTag;
-    }
 
     @VisibleForTesting
     protected long getInitialDelay() {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
new file mode 100644
index 0000000..3fcf0d4
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsQueryScheduler.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * The query scheduler class for calculating next query tasks parameters.
+ * <p>
+ * The class is not thread-safe and needs to be used on a consistent thread.
+ */
+public class MdnsQueryScheduler {
+
+    /**
+     * The argument for tracking the query tasks status.
+     */
+    public static class ScheduledQueryTaskArgs {
+        public final QueryTaskConfig config;
+        public final long timeToRun;
+        public final long minTtlExpirationTimeWhenScheduled;
+        public final long sessionId;
+
+        ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun,
+                long minTtlExpirationTimeWhenScheduled, long sessionId) {
+            this.config = config;
+            this.timeToRun = timeToRun;
+            this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
+            this.sessionId = sessionId;
+        }
+    }
+
+    @Nullable
+    private ScheduledQueryTaskArgs mLastScheduledQueryTaskArgs;
+
+    public MdnsQueryScheduler() {
+    }
+
+    /**
+     * Cancel the scheduled run. The method needed to be called when the scheduled task need to
+     * be canceled and rescheduling is not need.
+     */
+    public void cancelScheduledRun() {
+        mLastScheduledQueryTaskArgs = null;
+    }
+
+    /**
+     * Calculates ScheduledQueryTaskArgs for rescheduling the current task. Returns null if the
+     * rescheduling is not necessary.
+     */
+    @Nullable
+    public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(long now,
+            long minRemainingTtl, long lastSentTime, long sessionId) {
+        if (mLastScheduledQueryTaskArgs == null) {
+            return null;
+        }
+        if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
+            return null;
+        }
+
+        final long timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
+                mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime);
+
+        if (timeToRun <= mLastScheduledQueryTaskArgs.timeToRun) {
+            return null;
+        }
+
+        mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(mLastScheduledQueryTaskArgs.config,
+                timeToRun,
+                minRemainingTtl + now,
+                sessionId);
+        return mLastScheduledQueryTaskArgs;
+    }
+
+    /**
+     *  Calculates the ScheduledQueryTaskArgs for the next run.
+     */
+    @NonNull
+    public ScheduledQueryTaskArgs scheduleNextRun(
+            @NonNull QueryTaskConfig currentConfig,
+            long minRemainingTtl,
+            long now,
+            long lastSentTime,
+            long sessionId) {
+        final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun();
+        final long timeToRun;
+        if (mLastScheduledQueryTaskArgs == null) {
+            timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs;
+        } else {
+            timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
+                    nextRunConfig, now, minRemainingTtl, lastSentTime);
+        }
+        mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
+                minRemainingTtl + now,
+                sessionId);
+        return mLastScheduledQueryTaskArgs;
+    }
+
+    /**
+     *  Calculates the ScheduledQueryTaskArgs for the initial run.
+     */
+    public ScheduledQueryTaskArgs scheduleFirstRun(@NonNull QueryTaskConfig taskConfig,
+            long now, long minRemainingTtl, long currentSessionId) {
+        mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */,
+                now + minRemainingTtl/* minTtlExpirationTimeWhenScheduled */,
+                currentSessionId);
+        return mLastScheduledQueryTaskArgs;
+    }
+
+    private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
+            QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
+        final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
+        if (!queryTaskConfig.shouldUseQueryBackoff()) {
+            return lastSentTime + baseDelayInMs;
+        }
+        if (minRemainingTtl <= 0) {
+            // There's no service, or there is an expired service. In any case, schedule for the
+            // minimum time, which is the base delay.
+            return lastSentTime + baseDelayInMs;
+        }
+        // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
+        if (lastSentTime < now
+                && taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
+            // Use the original scheduling time if the TTL has not changed, to avoid continuously
+            // rescheduling to 80% of the remaining TTL as time passes
+            return taskArgs.timeToRun;
+        }
+        return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index 8bc598d..16c7d27 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -22,8 +22,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.util.Log;
 
+import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.MdnsRecordRepository.ReplyInfo;
 
 import java.io.IOException;
@@ -43,21 +43,21 @@
 public class MdnsReplySender {
     private static final boolean DBG = MdnsAdvertiser.DBG;
     private static final int MSG_SEND = 1;
-
-    private final String mLogTag;
     @NonNull
     private final MdnsInterfaceSocket mSocket;
     @NonNull
     private final Handler mHandler;
     @NonNull
     private final byte[] mPacketCreationBuffer;
+    @NonNull
+    private final SharedLog mSharedLog;
 
-    public MdnsReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
-            @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
+    public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
+            @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog) {
         mHandler = new SendHandler(looper);
-        mLogTag = MdnsReplySender.class.getSimpleName() + "/" +  interfaceTag;
         mSocket = socket;
         mPacketCreationBuffer = packetCreationBuffer;
+        mSharedLog = sharedLog;
     }
 
     /**
@@ -69,7 +69,7 @@
         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
 
         if (DBG) {
-            Log.v(mLogTag, "Scheduling " + reply);
+            mSharedLog.v("Scheduling " + reply);
         }
     }
 
@@ -134,7 +134,7 @@
         @Override
         public void handleMessage(@NonNull Message msg) {
             final ReplyInfo replyInfo = (ReplyInfo) msg.obj;
-            if (DBG) Log.v(mLogTag, "Sending " + replyInfo);
+            if (DBG) mSharedLog.v("Sending " + replyInfo);
 
             final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
             final MdnsPacket packet = new MdnsPacket(flags,
@@ -146,7 +146,7 @@
             try {
                 sendNow(packet, replyInfo.destination);
             } catch (IOException e) {
-                Log.e(mLogTag, "Error sending MDNS response", e);
+                mSharedLog.e("Error sending MDNS response", e);
             }
         }
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index a0a538e..2f10bde 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -23,7 +23,6 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
-import com.android.server.connectivity.mdns.util.MdnsLogger;
 import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.EOFException;
@@ -35,7 +34,6 @@
 public class MdnsResponseDecoder {
     public static final int SUCCESS = 0;
     private static final String TAG = "MdnsResponseDecoder";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
     private final boolean allowMultipleSrvRecordsPerHost =
             MdnsConfigs.allowMultipleSrvRecordsPerHost();
     @Nullable private final String[] serviceType;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index b5fd8a0..53a7ab9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -65,6 +65,7 @@
     @NonNull private final SocketKey socketKey;
     @NonNull private final SharedLog sharedLog;
     @NonNull private final Handler handler;
+    @NonNull private final MdnsQueryScheduler mdnsQueryScheduler;
     @NonNull private final Dependencies dependencies;
     /**
      * The service caches for each socket. It should be accessed from looper thread only.
@@ -82,9 +83,6 @@
     // QueryTask for
     // new subtypes. It stays the same between packets for same subtypes.
     private long currentSessionId = 0;
-
-    @Nullable
-    private ScheduledQueryTaskArgs lastScheduledQueryTaskArgs;
     private long lastSentTime;
 
     private class QueryTaskHandler extends Handler {
@@ -97,7 +95,8 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_START_QUERYTASK: {
-                    final ScheduledQueryTaskArgs taskArgs = (ScheduledQueryTaskArgs) msg.obj;
+                    final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs =
+                            (MdnsQueryScheduler.ScheduledQueryTaskArgs) msg.obj;
                     // QueryTask should be run immediately after being created (not be scheduled in
                     // advance). Because the result of "makeResponsesForResolve" depends on answers
                     // that were received before it is called, so to take into account all answers
@@ -126,15 +125,21 @@
 
                     tryRemoveServiceAfterTtlExpires();
 
-                    final QueryTaskConfig nextRunConfig =
-                            sentResult.taskArgs.config.getConfigForNextRun();
                     final long now = clock.elapsedRealtime();
                     lastSentTime = now;
                     final long minRemainingTtl = getMinRemainingTtl(now);
-                    final long timeToRun = calculateTimeToRun(lastScheduledQueryTaskArgs,
-                            nextRunConfig, now, minRemainingTtl, lastSentTime);
-                    scheduleNextRun(nextRunConfig, minRemainingTtl, now, timeToRun,
-                            lastScheduledQueryTaskArgs.sessionId);
+                    MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+                            mdnsQueryScheduler.scheduleNextRun(
+                                    sentResult.taskArgs.config,
+                                    minRemainingTtl,
+                                    now,
+                                    lastSentTime,
+                                    sentResult.taskArgs.sessionId
+                            );
+                    dependencies.sendMessageDelayed(
+                            handler,
+                            handler.obtainMessage(EVENT_START_QUERYTASK, args),
+                            calculateTimeToNextTask(args, now, sharedLog));
                     break;
                 }
                 default:
@@ -219,6 +224,7 @@
         this.handler = new QueryTaskHandler(looper);
         this.dependencies = dependencies;
         this.serviceCache = serviceCache;
+        this.mdnsQueryScheduler = new MdnsQueryScheduler();
     }
 
     private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
@@ -300,6 +306,7 @@
         }
         // Remove the next scheduled periodical task.
         removeScheduledTask();
+        mdnsQueryScheduler.cancelScheduledRun();
         // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
         // interested anymore.
         final QueryTaskConfig taskConfig = new QueryTaskConfig(
@@ -312,18 +319,25 @@
         if (lastSentTime == 0) {
             lastSentTime = now;
         }
+        final long minRemainingTtl = getMinRemainingTtl(now);
         if (hadReply) {
-            final QueryTaskConfig queryTaskConfig = taskConfig.getConfigForNextRun();
-            final long minRemainingTtl = getMinRemainingTtl(now);
-            final long timeToRun = now + queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
-            scheduleNextRun(
-                    queryTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
+            MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+                    mdnsQueryScheduler.scheduleNextRun(
+                            taskConfig,
+                            minRemainingTtl,
+                            now,
+                            lastSentTime,
+                            currentSessionId
+                    );
+            dependencies.sendMessageDelayed(
+                    handler,
+                    handler.obtainMessage(EVENT_START_QUERYTASK, args),
+                    calculateTimeToNextTask(args, now, sharedLog));
         } else {
             final List<MdnsResponse> servicesToResolve = makeResponsesForResolve(socketKey);
-            lastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */,
-                    now + getMinRemainingTtl(now)/* minTtlExpirationTimeWhenScheduled */,
-                    currentSessionId);
-            final QueryTask queryTask = new QueryTask(lastScheduledQueryTaskArgs, servicesToResolve,
+            final QueryTask queryTask = new QueryTask(
+                    mdnsQueryScheduler.scheduleFirstRun(taskConfig, now,
+                            minRemainingTtl, currentSessionId), servicesToResolve,
                     servicesToResolve.size() < listeners.size() /* sendDiscoveryQueries */);
             executor.submit(queryTask);
         }
@@ -341,7 +355,6 @@
         sharedLog.log("Remove EVENT_START_QUERYTASK"
                 + ", current session: " + currentSessionId);
         ++currentSessionId;
-        lastScheduledQueryTaskArgs = null;
     }
 
     private boolean responseMatchesOptions(@NonNull MdnsResponse response,
@@ -378,6 +391,7 @@
         }
         if (listeners.isEmpty()) {
             removeScheduledTask();
+            mdnsQueryScheduler.cancelScheduledRun();
         }
         return listeners.isEmpty();
     }
@@ -421,18 +435,18 @@
                 serviceCache.addOrUpdateService(serviceType, socketKey, response);
             }
         }
-        if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)
-                && lastScheduledQueryTaskArgs != null
-                && lastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
+        if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)) {
             final long now = clock.elapsedRealtime();
             final long minRemainingTtl = getMinRemainingTtl(now);
-            final long timeToRun = calculateTimeToRun(lastScheduledQueryTaskArgs,
-                    lastScheduledQueryTaskArgs.config, now,
-                    minRemainingTtl, lastSentTime);
-            if (timeToRun > lastScheduledQueryTaskArgs.timeToRun) {
-                QueryTaskConfig lastTaskConfig = lastScheduledQueryTaskArgs.config;
+            MdnsQueryScheduler.ScheduledQueryTaskArgs args =
+                    mdnsQueryScheduler.maybeRescheduleCurrentRun(now, minRemainingTtl,
+                            lastSentTime, currentSessionId + 1);
+            if (args != null) {
                 removeScheduledTask();
-                scheduleNextRun(lastTaskConfig, minRemainingTtl, now, timeToRun, currentSessionId);
+                dependencies.sendMessageDelayed(
+                        handler,
+                        handler.obtainMessage(EVENT_START_QUERYTASK, args),
+                        calculateTimeToNextTask(args, now, sharedLog));
             }
         }
     }
@@ -464,6 +478,7 @@
             }
         }
         removeScheduledTask();
+        mdnsQueryScheduler.cancelScheduledRun();
     }
 
     private void onResponseModified(@NonNull MdnsResponse response) {
@@ -599,28 +614,14 @@
         }
     }
 
-    private static class ScheduledQueryTaskArgs {
-        private final QueryTaskConfig config;
-        private final long timeToRun;
-        private final long minTtlExpirationTimeWhenScheduled;
-        private final long sessionId;
-
-        ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun,
-                long minTtlExpirationTimeWhenScheduled, long sessionId) {
-            this.config = config;
-            this.timeToRun = timeToRun;
-            this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
-            this.sessionId = sessionId;
-        }
-    }
 
     private static class QuerySentArguments {
         private final int transactionId;
         private final List<String> subTypes = new ArrayList<>();
-        private final ScheduledQueryTaskArgs taskArgs;
+        private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
 
         QuerySentArguments(int transactionId, @NonNull List<String> subTypes,
-                @NonNull ScheduledQueryTaskArgs taskArgs) {
+                @NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs) {
             this.transactionId = transactionId;
             this.subTypes.addAll(subTypes);
             this.taskArgs = taskArgs;
@@ -629,12 +630,10 @@
 
     // A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
     private class QueryTask implements Runnable {
-
-        private final ScheduledQueryTaskArgs taskArgs;
+        private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs;
         private final List<MdnsResponse> servicesToResolve = new ArrayList<>();
         private final boolean sendDiscoveryQueries;
-
-        QueryTask(@NonNull ScheduledQueryTaskArgs taskArgs,
+        QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs,
                 @NonNull List<MdnsResponse> servicesToResolve, boolean sendDiscoveryQueries) {
             this.taskArgs = taskArgs;
             this.servicesToResolve.addAll(servicesToResolve);
@@ -657,7 +656,8 @@
                                 taskArgs.config.onlyUseIpv6OnIpv6OnlyNetworks,
                                 sendDiscoveryQueries,
                                 servicesToResolve,
-                                clock)
+                                clock,
+                                sharedLog)
                                 .call();
             } catch (RuntimeException e) {
                 sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
@@ -670,27 +670,6 @@
         }
     }
 
-    private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
-            QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
-        final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
-        if (!queryTaskConfig.shouldUseQueryBackoff()) {
-            return lastSentTime + baseDelayInMs;
-        }
-        if (minRemainingTtl <= 0) {
-            // There's no service, or there is an expired service. In any case, schedule for the
-            // minimum time, which is the base delay.
-            return lastSentTime + baseDelayInMs;
-        }
-        // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
-        if (lastSentTime < now
-                && taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
-            // Use the original scheduling time if the TTL has not changed, to avoid continuously
-            // rescheduling to 80% of the remaining TTL as time passes
-            return taskArgs.timeToRun;
-        }
-        return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
-    }
-
     private long getMinRemainingTtl(long now) {
         long minRemainingTtl = Long.MAX_VALUE;
         for (MdnsResponse response : serviceCache.getCachedServices(serviceType, socketKey)) {
@@ -710,19 +689,11 @@
         return minRemainingTtl == Long.MAX_VALUE ? 0 : minRemainingTtl;
     }
 
-    @NonNull
-    private void scheduleNextRun(@NonNull QueryTaskConfig nextRunConfig,
-            long minRemainingTtl,
-            long timeWhenScheduled, long timeToRun, long sessionId) {
-        lastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
-                minRemainingTtl + timeWhenScheduled, sessionId);
-        // The timeWhenScheduled could be greater than the timeToRun if the Runnable is delayed.
-        long timeToNextTasksWithBackoffInMs = Math.max(timeToRun - timeWhenScheduled, 0);
+    private static long calculateTimeToNextTask(MdnsQueryScheduler.ScheduledQueryTaskArgs args,
+            long now, SharedLog sharedLog) {
+        long timeToNextTasksWithBackoffInMs = Math.max(args.timeToRun - now, 0);
         sharedLog.log(String.format("Next run: sessionId: %d, in %d ms",
-                lastScheduledQueryTaskArgs.sessionId, timeToNextTasksWithBackoffInMs));
-        dependencies.sendMessageDelayed(
-                handler,
-                handler.obtainMessage(EVENT_START_QUERYTASK, lastScheduledQueryTaskArgs),
-                timeToNextTasksWithBackoffInMs);
+                args.sessionId, timeToNextTasksWithBackoffInMs));
+        return timeToNextTasksWithBackoffInMs;
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
index cdd9f76..d690032 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -21,7 +21,7 @@
 import android.net.Network;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -37,8 +37,6 @@
  * @see MulticastSocket for javadoc of each public method.
  */
 public class MdnsSocket {
-    private static final MdnsLogger LOGGER = new MdnsLogger("MdnsSocket");
-
     static final int INTERFACE_INDEX_UNSPECIFIED = -1;
     public static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
             new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
@@ -47,19 +45,22 @@
     private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
     private final MulticastSocket multicastSocket;
     private boolean isOnIPv6OnlyNetwork;
+    private final SharedLog sharedLog;
 
     public MdnsSocket(
-            @NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port)
+            @NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider, int port,
+            SharedLog sharedLog)
             throws IOException {
-        this(multicastNetworkInterfaceProvider, new MulticastSocket(port));
+        this(multicastNetworkInterfaceProvider, new MulticastSocket(port), sharedLog);
     }
 
     @VisibleForTesting
     MdnsSocket(@NonNull MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider,
-            MulticastSocket multicastSocket) throws IOException {
+            MulticastSocket multicastSocket, SharedLog sharedLog) throws IOException {
         this.multicastNetworkInterfaceProvider = multicastNetworkInterfaceProvider;
         this.multicastNetworkInterfaceProvider.startWatchingConnectivityChanges();
         this.multicastSocket = multicastSocket;
+        this.sharedLog = sharedLog;
         // RFC Spec: https://tools.ietf.org/html/rfc6762
         // Time to live is set 255, which is similar to the jMDNS implementation.
         multicastSocket.setTimeToLive(255);
@@ -130,7 +131,7 @@
         try {
             return multicastSocket.getNetworkInterface().getIndex();
         } catch (SocketException e) {
-            LOGGER.e("Failed to retrieve interface index for socket.", e);
+            sharedLog.e("Failed to retrieve interface index for socket.", e);
             return -1;
         }
     }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9c9812d..d18a19b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -27,7 +27,7 @@
 import android.text.format.DateUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
@@ -57,7 +57,6 @@
     private static final String CAST_SENDER_LOG_SOURCE = "CAST_SENDER_SDK";
     private static final String CAST_PREFS_NAME = "google_cast";
     private static final String PREF_CAST_SENDER_ID = "PREF_CAST_SENDER_ID";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
     private static final String MULTICAST_TYPE = "multicast";
     private static final String UNICAST_TYPE = "unicast";
 
@@ -105,8 +104,11 @@
     @Nullable private Timer logMdnsPacketTimer;
     private AtomicInteger packetsCount;
     @Nullable private Timer checkMulticastResponseTimer;
+    private final SharedLog sharedLog;
 
-    public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock) {
+    public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock,
+            SharedLog sharedLog) {
+        this.sharedLog = sharedLog;
         this.context = context;
         this.multicastLock = multicastLock;
         if (useSeparateSocketForUnicast) {
@@ -125,7 +127,7 @@
     @Override
     public synchronized void startDiscovery() throws IOException {
         if (multicastSocket != null) {
-            LOGGER.w("Discovery is already in progress.");
+            sharedLog.w("Discovery is already in progress.");
             return;
         }
 
@@ -136,11 +138,11 @@
         shouldStopSocketLoop = false;
         try {
             // TODO (changed when importing code): consider setting thread stats tag
-            multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT);
+            multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT, sharedLog);
             multicastSocket.joinGroup();
             if (useSeparateSocketForUnicast) {
                 // For unicast, use port 0 and the system will assign it with any available port.
-                unicastSocket = createMdnsSocket(0);
+                unicastSocket = createMdnsSocket(0, sharedLog);
             }
             multicastLock.acquire();
         } catch (IOException e) {
@@ -164,7 +166,7 @@
     @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
     @Override
     public void stopDiscovery() {
-        LOGGER.log("Stop discovery.");
+        sharedLog.log("Stop discovery.");
         if (multicastSocket == null && unicastSocket == null) {
             return;
         }
@@ -233,7 +235,7 @@
     private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse,
             boolean onlyUseIpv6OnIpv6OnlyNetworks) {
         if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
-            LOGGER.w("sendMdnsPacket() is called after discovery already stopped");
+            sharedLog.w("sendMdnsPacket() is called after discovery already stopped");
             return;
         }
 
@@ -260,7 +262,7 @@
 
     private void createAndStartSendThread() {
         if (sendThread != null) {
-            LOGGER.w("A socket thread already exists.");
+            sharedLog.w("A socket thread already exists.");
             return;
         }
         sendThread = new Thread(this::sendThreadMain);
@@ -270,7 +272,7 @@
 
     private void createAndStartReceiverThreads() {
         if (multicastReceiveThread != null) {
-            LOGGER.w("A multicast receiver thread already exists.");
+            sharedLog.w("A multicast receiver thread already exists.");
             return;
         }
         multicastReceiveThread =
@@ -292,12 +294,12 @@
     }
 
     private void triggerSendThread() {
-        LOGGER.log("Trigger send thread.");
+        sharedLog.log("Trigger send thread.");
         Thread sendThread = this.sendThread;
         if (sendThread != null) {
             sendThread.interrupt();
         } else {
-            LOGGER.w("Socket thread is null");
+            sharedLog.w("Socket thread is null");
         }
     }
 
@@ -314,9 +316,9 @@
     }
 
     private void waitForSendThreadToStop() {
-        LOGGER.log("wait For Send Thread To Stop");
+        sharedLog.log("wait For Send Thread To Stop");
         if (sendThread == null) {
-            LOGGER.w("socket thread is already dead.");
+            sharedLog.w("socket thread is already dead.");
             return;
         }
         waitForThread(sendThread);
@@ -331,7 +333,7 @@
                 thread.interrupt();
                 thread.join(waitMs);
                 if (thread.isAlive()) {
-                    LOGGER.w("Failed to join thread: " + thread);
+                    sharedLog.w("Failed to join thread: " + thread);
                 }
                 break;
             } catch (InterruptedException e) {
@@ -390,13 +392,13 @@
                 }
             }
         } finally {
-            LOGGER.log("Send thread stopped.");
+            sharedLog.log("Send thread stopped.");
             try {
                 if (multicastSocket != null) {
                     multicastSocket.leaveGroup();
                 }
             } catch (Exception t) {
-                LOGGER.e("Failed to leave the group.", t);
+                sharedLog.e("Failed to leave the group.", t);
             }
 
             // Close the socket first. This is the only way to interrupt a blocking receive.
@@ -409,7 +411,7 @@
                     unicastSocket.close();
                 }
             } catch (RuntimeException t) {
-                LOGGER.e("Failed to close the mdns socket.", t);
+                sharedLog.e("Failed to close the mdns socket.", t);
             }
         }
     }
@@ -439,11 +441,11 @@
                 }
             } catch (IOException e) {
                 if (!shouldStopSocketLoop) {
-                    LOGGER.e("Failed to receive mDNS packets.", e);
+                    sharedLog.e("Failed to receive mDNS packets.", e);
                 }
             }
         }
-        LOGGER.log("Receive thread stopped.");
+        sharedLog.log("Receive thread stopped.");
     }
 
     private int processResponsePacket(@NonNull DatagramPacket packet, String responseType,
@@ -454,7 +456,7 @@
         try {
             response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
         } catch (MdnsPacket.ParseException e) {
-            LOGGER.w(String.format("Error while decoding %s packet (%d): %d",
+            sharedLog.w(String.format("Error while decoding %s packet (%d): %d",
                     responseType, packetNumber, e.code));
             if (callback != null) {
                 callback.onFailedToParseMdnsResponse(packetNumber, e.code,
@@ -476,8 +478,9 @@
     }
 
     @VisibleForTesting
-    MdnsSocket createMdnsSocket(int port) throws IOException {
-        return new MdnsSocket(new MulticastNetworkInterfaceProvider(context), port);
+    MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
+        return new MdnsSocket(new MulticastNetworkInterfaceProvider(context, sharedLog), port,
+                sharedLog);
     }
 
     private void sendPackets(List<DatagramPacket> packets, MdnsSocket socket) {
@@ -487,7 +490,7 @@
                 break;
             }
             try {
-                LOGGER.log("Sending a %s mDNS packet...", requestType);
+                sharedLog.log(String.format("Sending a %s mDNS packet...", requestType));
                 socket.send(packet);
 
                 // Start the timer task to monitor the response.
@@ -516,7 +519,7 @@
                                                 }
                                                 if ((!receivedMulticastResponse)
                                                         && receivedUnicastResponse) {
-                                                    LOGGER.e(String.format(
+                                                    sharedLog.e(String.format(
                                                             "Haven't received multicast response"
                                                                     + " in the last %d ms.",
                                                             checkMulticastResponseIntervalMs));
@@ -531,7 +534,7 @@
                     }
                 }
             } catch (IOException e) {
-                LOGGER.e(String.format("Failed to send a %s mDNS packet.", requestType), e);
+                sharedLog.e(String.format("Failed to send a %s mDNS packet.", requestType), e);
             }
         }
         packets.clear();
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
index 6925b49..23c5a4d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -44,7 +44,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -118,7 +117,7 @@
 
             if (mWifiP2pTetherInterface != null) {
                 if (newP2pIface != null) {
-                    Log.wtf(TAG, "Wifi p2p interface is changed from " + mWifiP2pTetherInterface
+                    mSharedLog.wtf("Wifi p2p interface is changed from " + mWifiP2pTetherInterface
                             + " to " + newP2pIface + " without null broadcast");
                 }
                 // Remove the socket.
@@ -133,7 +132,7 @@
             if (newP2pIface != null && !socketAlreadyExists) {
                 // Create a socket for wifi p2p interface.
                 final int ifaceIndex =
-                        mDependencies.getNetworkInterfaceIndexByName(newP2pIface);
+                        mDependencies.getNetworkInterfaceIndexByName(newP2pIface, mSharedLog);
                 createSocket(LOCAL_NET, createLPForTetheredInterface(newP2pIface, ifaceIndex));
             }
         }
@@ -233,21 +232,23 @@
         /*** Create a MdnsInterfaceSocket */
         public MdnsInterfaceSocket createMdnsInterfaceSocket(
                 @NonNull NetworkInterface networkInterface, int port, @NonNull Looper looper,
-                @NonNull byte[] packetReadBuffer) throws IOException {
-            return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer);
+                @NonNull byte[] packetReadBuffer, @NonNull SharedLog sharedLog) throws IOException {
+            return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer,
+                    sharedLog);
         }
 
         /*** Get network interface by given interface name */
-        public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName) {
+        public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName,
+                @NonNull SharedLog sharedLog) {
             final NetworkInterface iface;
             try {
                 iface = NetworkInterface.getByName(ifaceName);
             } catch (SocketException e) {
-                Log.e(TAG, "Error querying interface", e);
+                sharedLog.e("Error querying interface", e);
                 return IFACE_IDX_NOT_EXIST;
             }
             if (iface == null) {
-                Log.e(TAG, "Interface not found: " + ifaceName);
+                sharedLog.e("Interface not found: " + ifaceName);
                 return IFACE_IDX_NOT_EXIST;
             }
             return iface.getIndex();
@@ -335,7 +336,7 @@
         ensureRunningOnHandlerThread(mHandler);
         mRequestStop = false; // Reset stop request flag.
         if (mMonitoringSockets) {
-            Log.d(TAG, "Already monitoring sockets.");
+            mSharedLog.v("Already monitoring sockets.");
             return;
         }
         mSharedLog.i("Start monitoring sockets.");
@@ -390,7 +391,7 @@
     public void requestStopWhenInactive() {
         ensureRunningOnHandlerThread(mHandler);
         if (!mMonitoringSockets) {
-            Log.d(TAG, "Monitoring sockets hasn't been started.");
+            mSharedLog.v("Monitoring sockets hasn't been started.");
             return;
         }
         mRequestStop = true;
@@ -410,7 +411,7 @@
         mActiveNetworksLinkProperties.put(network, lp);
         if (!matchRequestedNetwork(network)) {
             if (DBG) {
-                Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
+                mSharedLog.v("Ignore LinkProperties change. There is no request for the"
                         + " Network:" + network);
             }
             return;
@@ -428,7 +429,7 @@
             @NonNull final List<LinkAddress> updatedAddresses) {
         for (int i = 0; i < mTetherInterfaceSockets.size(); ++i) {
             String tetheringInterfaceName = mTetherInterfaceSockets.keyAt(i);
-            if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName)
+            if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName, mSharedLog)
                     == ifaceIndex) {
                 updateSocketInfoAddress(null /* network */,
                         mTetherInterfaceSockets.valueAt(i), updatedAddresses);
@@ -462,7 +463,7 @@
             // tethering are only created if there is a request for all networks (interfaces).
             // Therefore, only update the interface list and skip this change if no such request.
             if (DBG) {
-                Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
+                mSharedLog.v("Ignore tether interfaces change. There is no request for all"
                         + " networks.");
             }
             current.clear();
@@ -482,7 +483,7 @@
                 continue;
             }
 
-            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name);
+            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name, mSharedLog);
             createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex));
         }
         for (String name : interfaceDiff.removed) {
@@ -495,7 +496,7 @@
     private void createSocket(NetworkKey networkKey, LinkProperties lp) {
         final String interfaceName = lp.getInterfaceName();
         if (interfaceName == null) {
-            Log.e(TAG, "Can not create socket with null interface name.");
+            mSharedLog.e("Can not create socket with null interface name.");
             return;
         }
 
@@ -514,7 +515,7 @@
                 if (knownTransports != null) {
                     transports = knownTransports;
                 } else {
-                    Log.wtf(TAG, "transports is missing for key: " + networkKey);
+                    mSharedLog.wtf("transports is missing for key: " + networkKey);
                     transports = new int[0];
                 }
             }
@@ -525,7 +526,8 @@
             mSharedLog.log("Create socket on net:" + networkKey + ", ifName:" + interfaceName);
             final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
                     networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
-                    mPacketReadBuffer);
+                    mPacketReadBuffer, mSharedLog.forSubComponent(
+                            MdnsInterfaceSocket.class.getSimpleName() + "/" + interfaceName));
             final List<LinkAddress> addresses = lp.getLinkAddresses();
             final Network network =
                     networkKey == LOCAL_NET ? null : ((NetworkAsKey) networkKey).mNetwork;
@@ -637,7 +639,7 @@
             final LinkProperties lp = mActiveNetworksLinkProperties.get(network);
             if (lp == null) {
                 // The requested network is not existed. Maybe wait for LinkProperties change later.
-                if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
+                if (DBG) mSharedLog.v("There is no LinkProperties for this network:" + network);
                 return;
             }
             createSocket(new NetworkAsKey(network), lp);
@@ -652,7 +654,8 @@
     private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
         final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
         if (socketInfo == null) {
-            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName);
+            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName,
+                    mSharedLog);
             createSocket(
                     LOCAL_NET,
                     createLPForTetheredInterface(interfaceName, ifaceIndex));
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
index f248c98..da82e96 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
@@ -22,7 +22,7 @@
 import android.net.Network;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.net.module.util.SharedLog;
 
 import java.io.IOException;
 import java.net.Inet4Address;
@@ -41,7 +41,7 @@
 public class MulticastNetworkInterfaceProvider {
 
     private static final String TAG = "MdnsNIProvider";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
+    private final SharedLog sharedLog;
     private static final boolean PREFER_IPV6 = MdnsConfigs.preferIpv6();
 
     private final List<NetworkInterfaceWrapper> multicastNetworkInterfaces = new ArrayList<>();
@@ -51,10 +51,12 @@
     private volatile boolean connectivityChanged = true;
 
     @SuppressWarnings("nullness:methodref.receiver.bound")
-    public MulticastNetworkInterfaceProvider(@NonNull Context context) {
+    public MulticastNetworkInterfaceProvider(@NonNull Context context,
+            @NonNull SharedLog sharedLog) {
+        this.sharedLog = sharedLog;
         // IMPORT CHANGED
         this.connectivityMonitor = new ConnectivityMonitorWithConnectivityManager(
-                context, this::onConnectivityChanged);
+                context, this::onConnectivityChanged, sharedLog);
     }
 
     private synchronized void onConnectivityChanged() {
@@ -83,7 +85,7 @@
             connectivityChanged = false;
             updateMulticastNetworkInterfaces();
             if (multicastNetworkInterfaces.isEmpty()) {
-                LOGGER.log("No network interface available for mDNS scanning.");
+                sharedLog.log("No network interface available for mDNS scanning.");
             }
         }
         return new ArrayList<>(multicastNetworkInterfaces);
@@ -93,7 +95,7 @@
         multicastNetworkInterfaces.clear();
         List<NetworkInterfaceWrapper> networkInterfaceWrappers = getNetworkInterfaces();
         for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaceWrappers) {
-            if (canScanOnInterface(interfaceWrapper)) {
+            if (canScanOnInterface(interfaceWrapper, sharedLog)) {
                 multicastNetworkInterfaces.add(interfaceWrapper);
             }
         }
@@ -133,10 +135,10 @@
                 }
             }
         } catch (SocketException e) {
-            LOGGER.e("Failed to get network interfaces.", e);
+            sharedLog.e("Failed to get network interfaces.", e);
         } catch (NullPointerException e) {
             // Android R has a bug that could lead to a NPE. See b/159277702.
-            LOGGER.e("Failed to call getNetworkInterfaces API", e);
+            sharedLog.e("Failed to call getNetworkInterfaces API", e);
         }
 
         return networkInterfaceWrappers;
@@ -148,7 +150,8 @@
     }
 
     /*** Check whether given network interface can support mdns */
-    private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
+    private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface,
+            @NonNull SharedLog sharedLog) {
         try {
             if ((networkInterface == null)
                     || networkInterface.isLoopback()
@@ -160,7 +163,7 @@
             }
             return hasInet4Address(networkInterface) || hasInet6Address(networkInterface);
         } catch (IOException e) {
-            LOGGER.e(String.format("Failed to check interface %s.",
+            sharedLog.e(String.format("Failed to check interface %s.",
                     networkInterface.getNetworkInterface().getDisplayName()), e);
         }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
index c21c903..6f16436 100644
--- a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -20,7 +20,6 @@
 import android.net.LinkAddress;
 import android.os.Handler;
 import android.system.OsConstants;
-import android.util.Log;
 
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.ip.NetlinkMonitor;
@@ -37,6 +36,8 @@
 public class SocketNetlinkMonitor extends NetlinkMonitor implements AbstractSocketNetlinkMonitor {
 
     public static final String TAG = SocketNetlinkMonitor.class.getSimpleName();
+    @NonNull
+    private final SharedLog mSharedLog;
 
     @NonNull
     private final MdnsSocketProvider.NetLinkMonitorCallBack mCb;
@@ -46,6 +47,7 @@
         super(handler, log, TAG, OsConstants.NETLINK_ROUTE,
                 NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
         mCb = cb;
+        mSharedLog = log;
     }
     @Override
     public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
@@ -71,7 +73,7 @@
                 mCb.deleteInterfaceAddress(ifaddrMsg.index, la);
                 break;
             default:
-                Log.e(TAG, "Unknown rtnetlink address msg type " + msg.getHeader().nlmsg_type);
+                mSharedLog.e("Unknown rtnetlink address msg type " + msg.getHeader().nlmsg_type);
         }
     }
 
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 6635fd3..3f4113a 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -3303,6 +3303,7 @@
 
     private static native long nativeGetTotalStat(int type);
     private static native long nativeGetIfaceStat(String iface, int type);
+    private static native long nativeGetIfIndexStat(int ifindex, int type);
     private static native long nativeGetUidStat(int uid, int type);
 
     /** Initializes and registers the Perfetto Network Trace data source */
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index a367d9d..e1e2585 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -24,9 +24,12 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MTU;
+import static com.android.net.module.util.NetworkStackConstants.IP_MTU;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TargetApi;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -35,6 +38,7 @@
 import android.net.TrafficStats;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.NetworkConstants;
+import android.os.Build;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -213,14 +217,10 @@
             mLinkProperties.addDnsServer(TEST_DNS6);
         }
 
-        final int lpMtu = mLinkProperties.getMtu();
-        final int mtu = lpMtu > 0 ? lpMtu : ETHER_MTU;
         for (RouteInfo route : mLinkProperties.getRoutes()) {
             if (route.getType() == RouteInfo.RTN_UNICAST && route.hasGateway()) {
-                InetAddress gateway = route.getGateway();
-                // Use mtu in the route if exists. Otherwise, use the one in the link property.
-                final int routeMtu = route.getMtu();
-                prepareIcmpMeasurements(gateway, (routeMtu > 0) ? routeMtu : mtu);
+                final InetAddress gateway = route.getGateway();
+                prepareIcmpMeasurements(gateway);
                 if (route.isIPv6Default()) {
                     prepareExplicitSourceIcmpMeasurements(gateway);
                 }
@@ -228,7 +228,7 @@
         }
 
         for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
-            prepareIcmpMeasurements(nameserver, mtu);
+            prepareIcmpMeasurements(nameserver);
             prepareDnsMeasurement(nameserver);
 
             // Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode,
@@ -285,24 +285,29 @@
             // calculation.
             if (addr instanceof Inet6Address) {
                 return IPV6_HEADER_LEN + ICMP_HEADER_LEN;
+            } else {
+                return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
             }
         } catch (UnknownHostException e) {
-            Log.e(TAG, "Create InetAddress fail(" + target + "): " + e);
+            throw new AssertionError("Create InetAddress fail(" + target + ")", e);
         }
-
-        return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
     }
 
-    private void prepareIcmpMeasurements(@NonNull InetAddress target, int targetNetworkMtu) {
+    private void prepareIcmpMeasurements(@NonNull InetAddress target) {
+        int mtu = getMtuForTarget(target);
+        // If getMtuForTarget fails, it doesn't matter what mtu is used because connect can't
+        // succeed anyway
+        if (mtu <= 0) mtu = mLinkProperties.getMtu();
+        if (mtu <= 0) mtu = ETHER_MTU;
         // Test with different size payload ICMP.
         // 1. Test with 0 payload.
         addPayloadIcmpMeasurement(target, 0);
         final int header = getHeaderLen(target);
         // 2. Test with full size MTU.
-        addPayloadIcmpMeasurement(target, targetNetworkMtu - header);
+        addPayloadIcmpMeasurement(target, mtu - header);
         // 3. If v6, make another measurement with the full v6 min MTU, unless that's what
         //    was done above.
-        if ((target instanceof Inet6Address) && (targetNetworkMtu != IPV6_MIN_MTU)) {
+        if ((target instanceof Inet6Address) && (mtu != IPV6_MIN_MTU)) {
             addPayloadIcmpMeasurement(target, IPV6_MIN_MTU - header);
         }
     }
@@ -321,6 +326,35 @@
         }
     }
 
+    /**
+     * Open a socket to the target address and return the mtu from that socket
+     *
+     * If the MTU can't be obtained for some reason (e.g. the target is unreachable) this will
+     * return -1.
+     *
+     * @param target the destination address
+     * @return the mtu to that destination, or -1
+     */
+    // getsockoptInt is S+, but this service code and only installs on S, so it's safe to ignore
+    // the lint warnings by using @TargetApi.
+    @TargetApi(Build.VERSION_CODES.S)
+    private int getMtuForTarget(InetAddress target) {
+        final int family = target instanceof Inet4Address ? AF_INET : AF_INET6;
+        try {
+            final FileDescriptor socket = Os.socket(family, SOCK_DGRAM, 0);
+            mNetwork.bindSocket(socket);
+            Os.connect(socket, target, 0);
+            if (family == AF_INET) {
+                return Os.getsockoptInt(socket, IPPROTO_IP, IP_MTU);
+            } else {
+                return Os.getsockoptInt(socket, IPPROTO_IPV6, IPV6_MTU);
+            }
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Can't get MTU for destination " + target, e);
+            return -1;
+        }
+    }
+
     private void prepareExplicitSourceIcmpMeasurements(InetAddress target) {
         for (LinkAddress l : mLinkProperties.getLinkAddresses()) {
             InetAddress source = l.getAddress();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 6b21dac..1c99722 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -29,6 +29,7 @@
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,9 +53,9 @@
 import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.PowerManager;
 import android.os.RemoteCallback;
 import android.os.SystemClock;
-import android.os.PowerManager;
 import android.provider.DeviceConfig;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
@@ -180,6 +181,12 @@
         mServiceClient.bind();
         mPowerManager = mContext.getSystemService(PowerManager.class);
         executeShellCommand("cmd netpolicy start-watching " + mUid);
+        // Some of the test cases assume that Data saver mode is initially disabled, which might not
+        // always be the case. Therefore, explicitly disable it before running the tests.
+        // Invoke setRestrictBackgroundInternal() directly instead of going through
+        // setRestrictBackground(), as some devices do not fully support the Data saver mode but
+        // still have certain parts of it enabled by default.
+        setRestrictBackgroundInternal(false);
         setAppIdle(false);
         mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index deca6a2..8c38b44 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -344,7 +344,7 @@
         setRestrictBackgroundInternal(enabled);
     }
 
-    private static void setRestrictBackgroundInternal(boolean enabled) {
+    static void setRestrictBackgroundInternal(boolean enabled) {
         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
         final String output = executeShellCommand("cmd netpolicy get restrict-background");
         final String expectedSuffix = enabled ? "enabled" : "disabled";
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 49620b0..7731ac9 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -16,6 +16,7 @@
 package android.net.cts
 
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.Manifest.permission.NETWORK_SETTINGS
 import android.app.compat.CompatChanges
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
@@ -60,6 +61,8 @@
 import android.net.nsd.NsdManager.RegistrationListener
 import android.net.nsd.NsdManager.ResolveListener
 import android.net.nsd.NsdServiceInfo
+import android.net.nsd.OffloadEngine
+import android.net.nsd.OffloadServiceInfo
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
@@ -353,6 +356,22 @@
         }
     }
 
+    private class TestNsdOffloadEngine : OffloadEngine,
+        NsdRecord<TestNsdOffloadEngine.OffloadEvent>() {
+        sealed class OffloadEvent : NsdEvent {
+            data class AddOrUpdateEvent(val info: OffloadServiceInfo) : OffloadEvent()
+            data class RemoveEvent(val info: OffloadServiceInfo) : OffloadEvent()
+        }
+
+        override fun onOffloadServiceUpdated(info: OffloadServiceInfo) {
+            add(OffloadEvent.AddOrUpdateEvent(info))
+        }
+
+        override fun onOffloadServiceRemoved(info: OffloadServiceInfo) {
+            add(OffloadEvent.RemoveEvent(info))
+        }
+    }
+
     @Before
     fun setUp() {
         handlerThread.start()
@@ -858,6 +877,52 @@
         }
     }
 
+    fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) {
+        assertEquals(serviceName, serviceInfo.key.serviceName)
+        assertEquals(serviceType, serviceInfo.key.serviceType)
+        assertEquals(listOf<String>("_subtype"), serviceInfo.subtypes)
+        assertTrue(serviceInfo.hostname.startsWith("Android_"))
+        assertTrue(serviceInfo.hostname.endsWith("local"))
+        assertEquals(0, serviceInfo.priority)
+        assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType)
+    }
+
+    @Test
+    fun testNsdManager_registerOffloadEngine() {
+        val targetSdkVersion = context.packageManager
+            .getTargetSdkVersion(context.applicationInfo.packageName)
+        // The offload callbacks are only supported with the new backend,
+        // enabled with target SDK U+.
+        assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+        val offloadEngine = TestNsdOffloadEngine()
+        runAsShell(NETWORK_SETTINGS) {
+            nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
+                OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
+                OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
+                { it.run() }, offloadEngine)
+        }
+
+        val si = NsdServiceInfo()
+        si.serviceType = "$serviceType,_subtype"
+        si.serviceName = serviceName
+        si.network = testNetwork1.network
+        si.port = 12345
+        val record = NsdRegistrationRecord()
+        nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
+        val addOrUpdateEvent = offloadEngine
+            .expectCallback<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent>()
+        checkOffloadServiceInfo(addOrUpdateEvent.info)
+
+        nsdManager.unregisterService(record)
+        val unregisterEvent = offloadEngine
+            .expectCallback<TestNsdOffloadEngine.OffloadEvent.RemoveEvent>()
+        checkOffloadServiceInfo(unregisterEvent.info)
+
+        runAsShell(NETWORK_SETTINGS) {
+            nsdManager.unregisterOffloadEngine(offloadEngine)
+        }
+    }
+
     private fun checkConnectSocketToMdnsd(shouldFail: Boolean) {
         val discoveryRecord = NsdDiscoveryRecord()
         val localSocket = LocalSocket()
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
index 8fb7be1..bb59e0d 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
@@ -31,6 +31,7 @@
 import android.net.Network;
 import android.net.NetworkRequest;
 
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -49,6 +50,7 @@
     @Mock private Context mContext;
     @Mock private ConnectivityMonitor.Listener mockListener;
     @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private SharedLog sharedLog;
 
     private ConnectivityMonitorWithConnectivityManager monitor;
 
@@ -57,7 +59,7 @@
         MockitoAnnotations.initMocks(this);
         doReturn(mConnectivityManager).when(mContext)
                 .getSystemService(Context.CONNECTIVITY_SERVICE);
-        monitor = new ConnectivityMonitorWithConnectivityManager(mContext, mockListener);
+        monitor = new ConnectivityMonitorWithConnectivityManager(mContext, mockListener, sharedLog);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 6a0334f..9b38fea 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -20,6 +20,8 @@
 import android.net.LinkAddress
 import android.net.Network
 import android.net.nsd.NsdServiceInfo
+import android.net.nsd.OffloadEngine
+import android.net.nsd.OffloadServiceInfo
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
@@ -60,6 +62,8 @@
 private val TEST_SOCKETKEY_2 = SocketKey(1002 /* interfaceIndex */)
 private val TEST_HOSTNAME = arrayOf("Android_test", "local")
 private const val TEST_SUBTYPE = "_subtype"
+private val TEST_INTERFACE1 = "test_iface1"
+private val TEST_INTERFACE2 = "test_iface2"
 
 private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
     port = 12345
@@ -94,6 +98,24 @@
         network = null
     }
 
+private val OFFLOAD_SERVICEINFO = OffloadServiceInfo(
+    OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+    listOf(TEST_SUBTYPE),
+    "Android_test.local",
+    null, /* rawOffloadPacket */
+    0, /* priority */
+    OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
+private val OFFLOAD_SERVICEINFO_NO_SUBTYPE = OffloadServiceInfo(
+    OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"),
+    listOf(),
+    "Android_test.local",
+    null, /* rawOffloadPacket */
+    0, /* priority */
+    OffloadEngine.OFFLOAD_TYPE_REPLY.toLong()
+)
+
 @RunWith(DevSdkIgnoreRunner::class)
 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class MdnsAdvertiserTest {
@@ -123,6 +145,8 @@
         doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
         doReturn(createEmptyNetworkInterface()).`when`(mockSocket1).getInterface()
         doReturn(createEmptyNetworkInterface()).`when`(mockSocket2).getInterface()
+        doReturn(TEST_INTERFACE1).`when`(mockInterfaceAdvertiser1).socketInterfaceName
+        doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName
     }
 
     @After
@@ -160,12 +184,15 @@
         )
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
-        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+        postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
                 mockInterfaceAdvertiser1, SERVICE_ID_1) }
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
 
         postSync { socketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) }
         verify(mockInterfaceAdvertiser1).destroyNow()
+        postSync { intAdvCbCaptor.value.onDestroyed(mockSocket1) }
+        verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE))
     }
 
     @Test
@@ -195,14 +222,16 @@
                 anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
-        postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
+        postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded(
                 mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
 
         // Need both advertisers to finish probing and call onRegisterServiceSucceeded
         verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any())
         doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1)
-        postSync { intAdvCbCaptor2.value.onRegisterServiceSucceeded(
+        postSync { intAdvCbCaptor2.value.onServiceProbingSucceeded(
                 mockInterfaceAdvertiser2, SERVICE_ID_1) }
+        verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
                 argThat { it.matches(ALL_NETWORKS_SERVICE) })
 
@@ -210,6 +239,8 @@
         postSync { advertiser.removeService(SERVICE_ID_1) }
         verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
         verify(mockInterfaceAdvertiser2).removeService(SERVICE_ID_1)
+        verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO))
+        verify(cb).onOffloadStop(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO))
 
         // Interface advertisers call onDestroyed after sending exit announcements
         postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) }
@@ -285,12 +316,12 @@
             argThat { it.matches(expectedCaseInsensitiveRenamed) }, eq(null))
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
-        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+        postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
                 mockInterfaceAdvertiser1, SERVICE_ID_1) }
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
 
         doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
-        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+        postSync { intAdvCbCaptor.value.onServiceProbingSucceeded(
                 mockInterfaceAdvertiser1, SERVICE_ID_2) }
         verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_2),
                 argThat { it.matches(expectedRenamed) })
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 7c6cb3e..12faa50 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -21,6 +21,7 @@
 import android.os.HandlerThread
 import android.os.SystemClock
 import com.android.internal.util.HexDump
+import com.android.net.module.util.SharedLog
 import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
 import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
 import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
@@ -52,6 +53,7 @@
 
     private val thread = HandlerThread(MdnsAnnouncerTest::class.simpleName)
     private val socket = mock(MdnsInterfaceSocket::class.java)
+    private val sharedLog = mock(SharedLog::class.java)
     private val buffer = ByteArray(1500)
 
     @Before
@@ -80,11 +82,11 @@
 
     @Test
     fun testAnnounce() {
-        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+        val replySender = MdnsReplySender( thread.looper, socket, buffer, sharedLog)
         @Suppress("UNCHECKED_CAST")
         val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
                 as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
-        val announcer = MdnsAnnouncer("testiface", thread.looper, replySender, cb)
+        val announcer = MdnsAnnouncer(thread.looper, replySender, cb, sharedLog)
         /*
         The expected packet replicates records announced when registering a service, as observed in
         the legacy mDNS implementation (some ordering differs to be more readable).
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index dd458b8..c19747e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -108,9 +108,9 @@
         doReturn(repository).`when`(deps).makeRecordRepository(any(),
             eq(TEST_HOSTNAME)
         )
-        doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any())
-        doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any())
-        doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any())
+        doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any(), any())
+        doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any(), any())
+        doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any(), any())
 
         val knownServices = mutableSetOf<Int>()
         doAnswer { inv ->
@@ -132,8 +132,8 @@
         advertiser.start()
 
         verify(socket).addPacketHandler(packetHandlerCaptor.capture())
-        verify(deps).makeMdnsProber(any(), any(), any(), probeCbCaptor.capture())
-        verify(deps).makeMdnsAnnouncer(any(), any(), any(), announceCbCaptor.capture())
+        verify(deps).makeMdnsProber(any(), any(), any(), probeCbCaptor.capture(), any())
+        verify(deps).makeMdnsAnnouncer(any(), any(), any(), announceCbCaptor.capture(), any())
     }
 
     @After
@@ -150,7 +150,7 @@
                 0L /* initialDelayMs */)
 
         thread.waitForIdle(TIMEOUT_MS)
-        verify(cb).onRegisterServiceSucceeded(advertiser, TEST_SERVICE_ID_1)
+        verify(cb).onServiceProbingSucceeded(advertiser, TEST_SERVICE_ID_1)
 
         // Remove the service: expect exit announcements
         val testExitInfo = mock(ExitAnnouncementInfo::class.java)
@@ -256,7 +256,7 @@
         val mockProbingInfo = mock(ProbingInfo::class.java)
         doReturn(mockProbingInfo).`when`(repository).setServiceProbing(TEST_SERVICE_ID_1)
 
-        advertiser.restartProbingForConflict(TEST_SERVICE_ID_1)
+        advertiser.maybeRestartProbingForConflict(TEST_SERVICE_ID_1)
 
         verify(prober).restartForConflict(mockProbingInfo)
     }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 29de272..3701b0c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -36,6 +36,7 @@
 import android.os.HandlerThread;
 
 import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -66,6 +67,7 @@
     @Mock private MdnsServiceBrowserListener mListener;
     @Mock private MdnsSocketClientBase.Callback mCallback;
     @Mock private SocketCreationCallback mSocketCreationCallback;
+    @Mock private SharedLog mSharedLog;
     private MdnsMultinetworkSocketClient mSocketClient;
     private Handler mHandler;
     private SocketKey mSocketKey;
@@ -78,7 +80,7 @@
         thread.start();
         mHandler = new Handler(thread.getLooper());
         mSocketKey = new SocketKey(1000 /* interfaceIndex */);
-        mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider);
+        mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider, mSharedLog);
         mHandler.post(() -> mSocketClient.setCallback(mCallback));
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index 0a8d78d..5ca4dd6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -21,6 +21,7 @@
 import android.os.HandlerThread
 import android.os.Looper
 import com.android.internal.util.HexDump
+import com.android.net.module.util.SharedLog
 import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.DevSdkIgnoreRunner
@@ -55,6 +56,7 @@
 class MdnsProberTest {
     private val thread = HandlerThread(MdnsProberTest::class.simpleName)
     private val socket = mock(MdnsInterfaceSocket::class.java)
+    private val sharedLog = mock(SharedLog::class.java)
     @Suppress("UNCHECKED_CAST")
     private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
         as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
@@ -82,8 +84,9 @@
     private class TestProber(
         looper: Looper,
         replySender: MdnsReplySender,
-        cb: PacketRepeaterCallback<ProbingInfo>
-    ) : MdnsProber("testiface", looper, replySender, cb) {
+        cb: PacketRepeaterCallback<ProbingInfo>,
+        sharedLog: SharedLog
+    ) : MdnsProber(looper, replySender, cb, sharedLog) {
         override fun getInitialDelay() = 0L
     }
 
@@ -116,8 +119,8 @@
 
     @Test
     fun testProbe() {
-        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
-        val prober = TestProber(thread.looper, replySender, cb)
+        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
         prober.startProbing(probeInfo)
@@ -140,8 +143,8 @@
 
     @Test
     fun testProbeMultipleRecords() {
-        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
-        val prober = TestProber(thread.looper, replySender, cb)
+        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(listOf(
                 makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
                 makeServiceRecord(TEST_SERVICE_NAME_2, 37891),
@@ -178,8 +181,8 @@
 
     @Test
     fun testStopProbing() {
-        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
-        val prober = TestProber(thread.looper, replySender, cb)
+        val replySender = MdnsReplySender(thread.looper, socket, buffer, sharedLog)
+        val prober = TestProber(thread.looper, replySender, cb, sharedLog)
         val probeInfo = TestProbeInfo(
                 listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),
                 // delayMs is the delay between each probe, so does not apply to the first one
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 69efc61..74f1c37 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -39,6 +39,7 @@
 import android.text.format.DateUtils;
 
 import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -74,6 +75,7 @@
     @Mock private MdnsSocket mockUnicastSocket;
     @Mock private MulticastLock mockMulticastLock;
     @Mock private MdnsSocketClient.Callback mockCallback;
+    @Mock private SharedLog sharedLog;
 
     private MdnsSocketClient mdnsClient;
 
@@ -84,9 +86,9 @@
         when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
                 .thenReturn(mockMulticastLock);
 
-        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
                     @Override
-                    MdnsSocket createMdnsSocket(int port) throws IOException {
+                    MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
                         if (port == MdnsConstants.MDNS_PORT) {
                             return mockMulticastSocket;
                         }
@@ -513,9 +515,9 @@
         //MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
 
         when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
-        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
                     @Override
-                    MdnsSocket createMdnsSocket(int port) {
+                    MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
                         if (port == MdnsConstants.MDNS_PORT) {
                             return mockMulticastSocket;
                         }
@@ -536,9 +538,9 @@
         //MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
 
         when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
-        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
+        mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
                     @Override
-                    MdnsSocket createMdnsSocket(int port) {
+                    MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
                         if (port == MdnsConstants.MDNS_PORT) {
                             return mockMulticastSocket;
                         }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index e971de7..c0b74e1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -152,11 +152,11 @@
                 .getNetworkInterfaceByName(WIFI_P2P_IFACE_NAME);
         doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
         doReturn(mock(MdnsInterfaceSocket.class))
-                .when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any());
+                .when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any(), any());
         doReturn(TETHERED_IFACE_IDX).when(mDeps).getNetworkInterfaceIndexByName(
-                TETHERED_IFACE_NAME);
+                eq(TETHERED_IFACE_NAME), any());
         doReturn(789).when(mDeps).getNetworkInterfaceIndexByName(
-                WIFI_P2P_IFACE_NAME);
+                eq(WIFI_P2P_IFACE_NAME), any());
         final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
         thread.start();
         mHandler = new Handler(thread.getLooper());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
index 73dbd38..5809684 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketTests.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -50,6 +51,7 @@
     @Mock private NetworkInterfaceWrapper mockNetworkInterfaceWrapper;
     @Mock private MulticastSocket mockMulticastSocket;
     @Mock private MulticastNetworkInterfaceProvider mockMulticastNetworkInterfaceProvider;
+    @Mock private SharedLog sharedLog;
     private SocketAddress socketIPv4Address;
     private SocketAddress socketIPv6Address;
 
@@ -75,7 +77,8 @@
 
     @Test
     public void mdnsSocket_basicFunctionality() throws IOException {
-        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+                sharedLog);
         mdnsSocket.send(datagramPacket);
         verify(mockMulticastSocket).setNetworkInterface(networkInterface);
         verify(mockMulticastSocket).send(datagramPacket);
@@ -101,7 +104,8 @@
         when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
                 .thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
 
-        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+                sharedLog);
 
         when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
                 Collections.singletonList(mockNetworkInterfaceWrapper)))
@@ -125,7 +129,8 @@
         when(mockMulticastNetworkInterfaceProvider.getMulticastNetworkInterfaces())
                 .thenReturn(Collections.singletonList(mockNetworkInterfaceWrapper));
 
-        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket);
+        mdnsSocket = new MdnsSocket(mockMulticastNetworkInterfaceProvider, mockMulticastSocket,
+                sharedLog);
 
         when(mockMulticastNetworkInterfaceProvider.isOnIpV6OnlyNetwork(
                 Collections.singletonList(mockNetworkInterfaceWrapper)))
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
index 2268dfe..af233c9 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProviderTests.java
@@ -30,6 +30,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.net.module.util.SharedLog;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -65,6 +66,8 @@
     @Mock private NetworkInterfaceWrapper multicastInterfaceOne;
     @Mock private NetworkInterfaceWrapper multicastInterfaceTwo;
 
+    @Mock private SharedLog sharedLog;
+
     private final List<NetworkInterfaceWrapper> networkInterfaces = new ArrayList<>();
     private MulticastNetworkInterfaceProvider provider;
     private Context context;
@@ -156,7 +159,7 @@
                 false /* isIpv6 */);
 
         provider =
-                new MulticastNetworkInterfaceProvider(context) {
+                new MulticastNetworkInterfaceProvider(context, sharedLog) {
                     @Override
                     List<NetworkInterfaceWrapper> getNetworkInterfaces() {
                         return networkInterfaces;