Snap for 11685116 from 4b113655fda72a00e3d793de4cc1cec9e6e1614f to mainline-extservices-release

Change-Id: I35dcbc198c4c3044d73ee7d1b947909850754aaa
diff --git a/Android.bp b/Android.bp
index ae31237..f6ba8a5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["external_ot-br-posix_license"],
 }
 
@@ -133,16 +134,27 @@
         "-DOTBR_CONFIG_ANDROID_VERSION_HEADER_ENABLE=1",
         "-DOTBR_CONFIG_FILE=\"src/android/otbr-config-android.h\"",
         "-DOTBR_ENABLE_VENDOR_SERVER=1", // for OtDaemonServer
+        "-DOTBR_ENABLE_BACKBONE_ROUTER=1",
         "-DOTBR_ENABLE_BORDER_ROUTING=1",
+        "-DOTBR_ENABLE_BORDER_ROUTING_COUNTERS=1",
         "-DOTBR_ENABLE_BORDER_AGENT=1",
         "-DOTBR_ENABLE_PUBLISH_MESHCOP_BA_ID=1",
         // Used for bypassing the macro check. In fact mdnssd is not used because we don't compile
         // the related source files.
         "-DOTBR_ENABLE_MDNS_MDNSSD=1",
         "-DOTBR_ENABLE_SRP_ADVERTISING_PROXY=1",
-        "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=0",
+        "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=1",
         "-DOTBR_ENABLE_SRP_SERVER_AUTO_ENABLE_MODE=1",
         "-DOTBR_PACKAGE_NAME=\"OTBR_AGENT\"",
+        "-DOTBR_STOP_BORDER_AGENT_ON_INIT=1",
+        // The platform specific rules for selecting infrastructure link do not apply to Android
+        "-DOTBR_ENABLE_VENDOR_INFRA_LINK_SELECT=0",
+
+        // Disable 1.4 features, they are not supported on Android yet.
+        "-DOTBR_ENABLE_NAT64=0",
+        "-DOTBR_ENABLE_DNS_UPSTREAM_QUERY=0",
+        "-DOTBR_ENABLE_DHCP6_PD=0",
+        "-DOTBR_ENABLE_TREL=0",
     ],
 
     srcs: [
@@ -150,6 +162,7 @@
         "src/android/mdns_publisher.cpp",
         "src/android/otdaemon_server.cpp",
         "src/android/otdaemon_telemetry.cpp",
+        "src/backbone_router/backbone_agent.cpp",
         "src/border_agent/border_agent.cpp",
         "src/ncp/ncp_openthread.cpp",
         "src/sdp_proxy/advertising_proxy.cpp",
diff --git a/src/Android.bp b/src/Android.bp
index d647729..b9a3a54 100644
--- a/src/Android.bp
+++ b/src/Android.bp
@@ -26,6 +26,7 @@
 //  POSSIBILITY OF SUCH DAMAGE.
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["external_ot-br-posix_license"],
 }
 
diff --git a/src/agent/application.cpp b/src/agent/application.cpp
index 73da484..16f4082 100644
--- a/src/agent/application.cpp
+++ b/src/agent/application.cpp
@@ -104,8 +104,14 @@
     mPublisher->Start();
 #endif
 #if OTBR_ENABLE_BORDER_AGENT
+// This is for delaying publishing the MeshCoP service until the correct
+// vendor name and OUI etc. are correctly set by BorderAgent::SetMeshCopServiceValues()
+#if OTBR_STOP_BORDER_AGENT_ON_INIT
+    mBorderAgent.SetEnabled(false);
+#else
     mBorderAgent.SetEnabled(true);
 #endif
+#endif
 #if OTBR_ENABLE_BACKBONE_ROUTER
     mBackboneAgent.Init();
 #endif
diff --git a/src/android/aidl/Android.bp b/src/android/aidl/Android.bp
index f33ad8c..bf25b72 100644
--- a/src/android/aidl/Android.bp
+++ b/src/android/aidl/Android.bp
@@ -27,6 +27,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["external_ot-br-posix_license"],
 }
 
@@ -37,6 +38,7 @@
     unstable: true,
     srcs: [
         "com/android/**/*.aidl",
+        ":framework-thread-ot-daemon-shared-aidl-sources",
     ],
     backend: {
         java: {
@@ -61,5 +63,5 @@
         "//external/ot-br-posix:__subpackages__",
         "//packages/modules/ThreadNetwork/service:__subpackages__",
         "//system/tools/aidl:__subpackages__",
-    ]
+    ],
 }
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl
new file mode 100644
index 0000000..21de2d1
--- /dev/null
+++ b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ *    Copyright (c) 2024, The OpenThread Authors.
+ *    All rights reserved.
+ *
+ *    Redistribution and use in source and binary forms, with or without
+ *    modification, are permitted provided that the following conditions are met:
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *    3. Neither the name of the copyright holder nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *    POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.thread.openthread;
+
+/** Receives the information when a service instance is found/lost. */
+oneway interface INsdDiscoverServiceCallback {
+    void onServiceDiscovered(in String name,
+                             in String type,
+                             boolean isFound);
+}
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl
index 17f6295..84549f9 100644
--- a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl
+++ b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl
@@ -30,6 +30,8 @@
 
 import com.android.server.thread.openthread.DnsTxtAttribute;
 import com.android.server.thread.openthread.INsdStatusReceiver;
+import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
+import com.android.server.thread.openthread.INsdResolveServiceCallback;
 
 /**
  * The service which supports mDNS advertising and discovery by {@link NsdManager}.
@@ -91,4 +93,61 @@
      *                             identify the registration
      */
     void unregister(in INsdStatusReceiver receiver, int listenerId);
+
+    /** Resets the NsdPublisher, i.e. clear all registrations. */
+    void reset();
+
+    /**
+     * Discovers mDNS services of a specific type.
+     *
+     * <p>To stop discovering services, the caller must pass in the same listener ID which was used
+     * when starting discoverying the services.
+     *
+     * @param type the service type
+     * @param callback the callback when a service is found/lost
+     * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the
+     *                             service discovery operation
+     */
+    void discoverService(in String type,
+                         in INsdDiscoverServiceCallback callback,
+                         int listenerId);
+
+    /**
+     * Stops discovering services of a specific type.
+     *
+     * <p>To stop discovering services, the caller must pass in the same listener ID which was used
+     * when starting discoverying the services.
+     *
+     * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the
+     *                             service discovery operation
+     */
+    void stopServiceDiscovery(int listenerId);
+
+    /**
+     * Resolves an mDNS service instance.
+     *
+     * <p>To stop resolving a service, the caller must pass in the same listener ID which was used
+     * when starting resolving the service.
+     *
+     * @param name the service instance name
+     * @param type the service type
+     * @param callback the callback when a service is updated
+     * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the
+     *                             service resolution operation
+     */
+    void resolveService(in String name,
+                        in String type,
+                        in INsdResolveServiceCallback callback,
+                        int listenerId);
+
+    /**
+     * Stops resolving an mDNS service instance.
+     *
+     * <p>To stop resolving a service, the caller must pass in the same listener ID which was used
+     * when starting resolving the service.
+     *
+     * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the
+     *                             service resolution operation
+     */
+    void stopServiceResolution(int listenerId);
 }
diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl
new file mode 100644
index 0000000..48d7b5c
--- /dev/null
+++ b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl
@@ -0,0 +1,42 @@
+/*
+ *    Copyright (c) 2024, The OpenThread Authors.
+ *    All rights reserved.
+ *
+ *    Redistribution and use in source and binary forms, with or without
+ *    modification, are permitted provided that the following conditions are met:
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *    3. Neither the name of the copyright holder nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *    POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.thread.openthread;
+
+import com.android.server.thread.openthread.DnsTxtAttribute;
+
+/** Receives the information of a resolved service instance. */
+oneway interface INsdResolveServiceCallback {
+    void onServiceResolved(in String hostname,
+                           in String name,
+                           in String type,
+                           int port,
+                           in List<String> addresses,
+                           in List<DnsTxtAttribute> txt,
+                           int ttlSeconds);
+}
diff --git a/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl b/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
index 861983e..d07f72a 100644
--- a/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
+++ b/src/android/aidl/com/android/server/thread/openthread/IOtDaemon.aidl
@@ -30,12 +30,14 @@
 
 import android.os.ParcelFileDescriptor;
 
+import android.net.thread.ChannelMaxPower;
 import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
 import com.android.server.thread.openthread.IChannelMasksReceiver;
 import com.android.server.thread.openthread.Ipv6AddressInfo;
 import com.android.server.thread.openthread.IOtStatusReceiver;
 import com.android.server.thread.openthread.IOtDaemonCallback;
 import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.MeshcopTxtAttributes;
 
 /**
  * The OpenThread daemon service which provides access to the core Thread stack for
@@ -72,6 +74,7 @@
         OT_ERROR_BUSY = 5,
         OT_ERROR_PARSE = 6,
         OT_ERROR_ABORT = 11,
+        OT_ERROR_NOT_IMPLEMENTED = 12,
         OT_ERROR_INVALID_STATE = 13,
         OT_ERROR_RESPONSE_TIMEOUT = 28,
         OT_ERROR_REASSEMBLY_TIMEOUT = 30,
@@ -79,16 +82,30 @@
     }
 
     /**
-     * Initializes this service with Thread tunnel interface FD.
+     * Initializes this service.
+     *
+     * <p>This API MUST be called before all other APIs of this interface.
      *
      * @param tunFd the Thread tunnel interface FD which can be used to transmit/receive
      *              packets to/from Thread PAN
      * @param enabled the Thead enabled state from Persistent Settings
      * @param nsdPublisher the INsdPublisher which can be used for mDNS advertisement/discovery
-     *                    on AIL by {@link NsdManager}
+     *                     on AIL by {@link NsdManager}
+     * @param meshcopTxts the MeshCoP TXT values set by the system_server to override the default
+     *                    ones
+     * @param callback the callback for receiving OtDaemonState changes
+     * @param countryCode 2 bytes country code (as defined in ISO 3166) to set
      */
-    void initialize(in ParcelFileDescriptor tunFd, in boolean enabled,
-                    in INsdPublisher nsdPublisher);
+    void initialize(
+            in ParcelFileDescriptor tunFd,
+            in boolean enabled,
+            in INsdPublisher nsdPublisher,
+            in MeshcopTxtAttributes meshcopTxts,
+            in IOtDaemonCallback callback,
+            in String countryCode);
+
+    /** Terminates the ot-daemon process. */
+    void terminate();
 
     /**
      * Enables/disables Thread.
@@ -143,7 +160,7 @@
     /**
      * Sets the country code.
      *
-     * @param countryCode 2 byte country code (as defined in ISO 3166) to set.
+     * @param countryCode 2 bytes country code (as defined in ISO 3166) to set.
      * @param receiver the receiver to receive result of this operation
      */
     oneway void setCountryCode(in String countryCode, in IOtStatusReceiver receiver);
@@ -165,5 +182,13 @@
      */
     void getChannelMasks(in IChannelMasksReceiver receiver);
 
+   /**
+    * Sets the max power of each channel
+    *
+    * @param channelMaxPowers an array of {@code ChannelMaxPower}.
+    * @param receiver the receiver to the receive result of this operation.
+    */
+    void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOtStatusReceiver receiver);
+
     // TODO: add Border Router APIs
 }
diff --git a/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl b/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl
new file mode 100644
index 0000000..3ac1db8
--- /dev/null
+++ b/src/android/aidl/com/android/server/thread/openthread/MeshcopTxtAttributes.aidl
@@ -0,0 +1,57 @@
+/*
+ *    Copyright (c) 2024, The OpenThread Authors.
+ *    All rights reserved.
+ *
+ *    Redistribution and use in source and binary forms, with or without
+ *    modification, are permitted provided that the following conditions are met:
+ *    1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *    2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *    3. Neither the name of the copyright holder nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *    POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.server.thread.openthread;
+
+/**
+ *  A collection of MeshCoP TXT entries that are supplied by Android platform.
+ */
+parcelable MeshcopTxtAttributes {
+    /**
+     * Predefined MeshCoP TXT entry named "mn".
+     *
+     * The length must not exceed 24 UTF-8 bytes.
+     */
+    String modelName;
+
+    /**
+     * Predefined MeshCoP TXT entry named "vn".
+     *
+     * The length must not exceed 24 UTF-8 bytes.
+     */
+    String vendorName;
+
+    /**
+     * Predefined MeshCoP TXT entry named "vo".
+     *
+     * The length must be 3 bytes.
+     */
+    byte[] vendorOui;
+
+    // More vendor-specific (v*) TXT entries can be added here
+}
diff --git a/src/android/java/Android.bp b/src/android/java/Android.bp
index 89f7910..d02c726 100644
--- a/src/android/java/Android.bp
+++ b/src/android/java/Android.bp
@@ -27,6 +27,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["external_ot-br-posix_license"],
 }
 
diff --git a/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java b/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
index 96d5db0..ce50eba 100644
--- a/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
+++ b/src/android/java/com/android/server/thread/openthread/testing/FakeOtDaemon.java
@@ -33,6 +33,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.thread.ChannelMaxPower;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -46,6 +47,7 @@
 import com.android.server.thread.openthread.IOtDaemon;
 import com.android.server.thread.openthread.IOtDaemonCallback;
 import com.android.server.thread.openthread.IOtStatusReceiver;
+import com.android.server.thread.openthread.MeshcopTxtAttributes;
 import com.android.server.thread.openthread.OtDaemonState;
 
 import java.time.Duration;
@@ -67,35 +69,46 @@
     private static final long PROACTIVE_LISTENER_ID = -1;
 
     private final Handler mHandler;
-    private final OtDaemonState mState;
-    private final BackboneRouterState mBbrState;
-    private int mThreadEnabled = OT_STATE_ENABLED;
+    private OtDaemonState mState;
+    private BackboneRouterState mBbrState;
+    private boolean mIsInitialized = false;
+    private int mThreadEnabled = OT_STATE_DISABLED;
     private int mChannelMasksReceiverOtError = OT_ERROR_NONE;
     private int mSupportedChannelMask = 0x07FFF800; // from channel 11 to 26
     private int mPreferredChannelMask = 0;
 
     @Nullable private DeathRecipient mDeathRecipient;
-
     @Nullable private ParcelFileDescriptor mTunFd;
-
-    @NonNull private INsdPublisher mNsdPublisher;
-
+    @Nullable private INsdPublisher mNsdPublisher;
+    @Nullable private MeshcopTxtAttributes mOverriddenMeshcopTxts;
     @Nullable private IOtDaemonCallback mCallback;
-
     @Nullable private Long mCallbackListenerId;
-
     @Nullable private RemoteException mJoinException;
+    @Nullable private String mCountryCode;
 
     public FakeOtDaemon(Handler handler) {
         mHandler = handler;
+        resetStates();
+    }
+
+    private void resetStates() {
         mState = new OtDaemonState();
         mState.isInterfaceUp = false;
+        mState.partitionId = -1;
         mState.deviceRole = OT_DEVICE_ROLE_DISABLED;
         mState.activeDatasetTlvs = new byte[0];
         mState.pendingDatasetTlvs = new byte[0];
         mBbrState = new BackboneRouterState();
         mBbrState.multicastForwardingEnabled = false;
         mBbrState.listeningAddresses = new ArrayList<>();
+
+        mTunFd = null;
+        mThreadEnabled = OT_STATE_DISABLED;
+        mNsdPublisher = null;
+        mIsInitialized = false;
+
+        mCallback = null;
+        mCallbackListenerId = null;
     }
 
     @Override
@@ -122,23 +135,47 @@
         return true;
     }
 
-    @Override
-    public void initialize(ParcelFileDescriptor tunFd, boolean enabled, INsdPublisher nsdPublisher)
-            throws RemoteException {
-        mTunFd = tunFd;
-        mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
-        mNsdPublisher = nsdPublisher;
+    @Nullable
+    public DeathRecipient getDeathRecipient() {
+        return mDeathRecipient;
     }
 
     @Override
-    public void setThreadEnabled(boolean enabled, IOtStatusReceiver receiver) {
+    public void initialize(
+            ParcelFileDescriptor tunFd,
+            boolean enabled,
+            INsdPublisher nsdPublisher,
+            MeshcopTxtAttributes overriddenMeshcopTxts,
+            IOtDaemonCallback callback,
+            String countryCode)
+            throws RemoteException {
+        mIsInitialized = true;
+        mTunFd = tunFd;
+        mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
+        mNsdPublisher = nsdPublisher;
+        mCountryCode = countryCode;
+
+        mOverriddenMeshcopTxts = new MeshcopTxtAttributes();
+        mOverriddenMeshcopTxts.vendorOui = overriddenMeshcopTxts.vendorOui.clone();
+        mOverriddenMeshcopTxts.vendorName = overriddenMeshcopTxts.vendorName;
+        mOverriddenMeshcopTxts.modelName = overriddenMeshcopTxts.modelName;
+
+        registerStateCallback(callback, PROACTIVE_LISTENER_ID);
+    }
+
+    /** Returns {@code true} if {@link initialize} has been called to initialize this object. */
+    public boolean isInitialized() {
+        return mIsInitialized;
+    }
+
+    @Override
+    public void terminate() throws RemoteException {
         mHandler.post(
                 () -> {
-                    mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
-                    try {
-                        receiver.onSuccess();
-                    } catch (RemoteException e) {
-                        throw new AssertionError(e);
+                    resetStates();
+                    if (mDeathRecipient != null) {
+                        mDeathRecipient.binderDied();
+                        mDeathRecipient = null;
                     }
                 });
     }
@@ -147,6 +184,14 @@
         return mThreadEnabled;
     }
 
+    public OtDaemonState getState() {
+        return makeCopy(mState);
+    }
+
+    public BackboneRouterState getBackboneRouterState() {
+        return makeCopy(mBbrState);
+    }
+
     /**
      * Returns the Thread TUN interface FD sent to OT daemon or {@code null} if {@link initialize}
      * is never called.
@@ -165,6 +210,33 @@
         return mNsdPublisher;
     }
 
+    /**
+     * Returns the overridden MeshCoP TXT attributes that is to OT daemon or {@code null} if {@link
+     * #initialize} is never called.
+     */
+    @Nullable
+    public MeshcopTxtAttributes getOverriddenMeshcopTxtAttributes() {
+        return mOverriddenMeshcopTxts;
+    }
+
+    @Nullable
+    public IOtDaemonCallback getCallback() {
+        return mCallback;
+    }
+
+    @Override
+    public void setThreadEnabled(boolean enabled, IOtStatusReceiver receiver) {
+        mHandler.post(
+                () -> {
+                    mThreadEnabled = enabled ? OT_STATE_ENABLED : OT_STATE_DISABLED;
+                    try {
+                        receiver.onSuccess();
+                    } catch (RemoteException e) {
+                        throw new AssertionError(e);
+                    }
+                });
+    }
+
     @Override
     public void registerStateCallback(IOtDaemonCallback callback, long listenerId)
             throws RemoteException {
@@ -180,6 +252,15 @@
         return mCallback;
     }
 
+    /**
+     * Returns the country code sent to OT daemon or {@code null} if {@link #initialize} is never
+     * called.
+     */
+    @Nullable
+    public String getCountryCode() {
+        return mCountryCode;
+    }
+
     @Override
     public void join(byte[] activeDataset, IOtStatusReceiver receiver) throws RemoteException {
         if (mJoinException != null) {
@@ -210,15 +291,27 @@
                 JOIN_DELAY.toMillis());
     }
 
+    private OtDaemonState makeCopy(OtDaemonState state) {
+        OtDaemonState copyState = new OtDaemonState();
+        copyState.isInterfaceUp = state.isInterfaceUp;
+        copyState.deviceRole = state.deviceRole;
+        copyState.partitionId = state.partitionId;
+        copyState.activeDatasetTlvs = state.activeDatasetTlvs.clone();
+        copyState.pendingDatasetTlvs = state.pendingDatasetTlvs.clone();
+        return copyState;
+    }
+
+    private BackboneRouterState makeCopy(BackboneRouterState state) {
+        BackboneRouterState copyState = new BackboneRouterState();
+        copyState.multicastForwardingEnabled = state.multicastForwardingEnabled;
+        copyState.listeningAddresses = new ArrayList<>(state.listeningAddresses);
+        return copyState;
+    }
+
     private void onStateChanged(OtDaemonState state, long listenerId) {
         try {
             // Make a copy of state so that clients won't keep a direct reference to it
-            OtDaemonState copyState = new OtDaemonState();
-            copyState.isInterfaceUp = state.isInterfaceUp;
-            copyState.deviceRole = state.deviceRole;
-            copyState.partitionId = state.partitionId;
-            copyState.activeDatasetTlvs = state.activeDatasetTlvs.clone();
-            copyState.pendingDatasetTlvs = state.pendingDatasetTlvs.clone();
+            OtDaemonState copyState = makeCopy(state);
 
             mCallback.onStateChanged(copyState, listenerId);
         } catch (RemoteException e) {
@@ -229,9 +322,8 @@
     private void onBackboneRouterStateChanged(BackboneRouterState state) {
         try {
             // Make a copy of state so that clients won't keep a direct reference to it
-            BackboneRouterState copyState = new BackboneRouterState();
-            copyState.multicastForwardingEnabled = state.multicastForwardingEnabled;
-            copyState.listeningAddresses = new ArrayList<>(state.listeningAddresses);
+            BackboneRouterState copyState = makeCopy(state);
+
             mCallback.onBackboneRouterStateChanged(copyState);
         } catch (RemoteException e) {
             throw new AssertionError(e);
@@ -294,4 +386,11 @@
     public void setChannelMasksReceiverOtError(int otError) {
         mChannelMasksReceiverOtError = otError;
     }
+
+    @Override
+    public void setChannelMaxPowers(ChannelMaxPower[] channelMaxPowers, IOtStatusReceiver receiver)
+            throws RemoteException {
+        throw new UnsupportedOperationException(
+                "FakeOtDaemon#setChannelTargetPowers is not implemented!");
+    }
 }
diff --git a/src/android/mdns_publisher.cpp b/src/android/mdns_publisher.cpp
index 4bd26ae..6febecc 100644
--- a/src/android/mdns_publisher.cpp
+++ b/src/android/mdns_publisher.cpp
@@ -38,9 +38,6 @@
 }
 
 namespace Android {
-
-using Status = ::ndk::ScopedAStatus;
-
 otbrError DnsErrorToOtbrErrorImpl(int32_t aError)
 {
     return aError == 0 ? OTBR_ERROR_NONE : OTBR_ERROR_MDNS;
@@ -61,19 +58,69 @@
     return Status::ok();
 }
 
+Status MdnsPublisher::NsdDiscoverServiceCallback::onServiceDiscovered(const std::string &aName,
+                                                                      const std::string &aType,
+                                                                      bool               aIsFound)
+{
+    VerifyOrExit(aIsFound, mSubscription.mPublisher.OnServiceRemoved(0, aType, aName));
+
+    mSubscription.Resolve(aName, aType);
+
+exit:
+    return Status::ok();
+}
+
+Status MdnsPublisher::NsdResolveServiceCallback::onServiceResolved(const std::string                  &aHostname,
+                                                                   const std::string                  &aName,
+                                                                   const std::string                  &aType,
+                                                                   int                                 aPort,
+                                                                   const std::vector<std::string>     &aAddresses,
+                                                                   const std::vector<DnsTxtAttribute> &aTxt,
+                                                                   int                                 aTtlSeconds)
+{
+    DiscoveredInstanceInfo info;
+    TxtList                txtList;
+
+    info.mHostName = aHostname + ".local.";
+    info.mName     = aName;
+    info.mPort     = aPort;
+    info.mTtl      = std::clamp(aTtlSeconds, kMinResolvedTtl, kMaxResolvedTtl);
+    for (const auto &addressStr : aAddresses)
+    {
+        Ip6Address address;
+        int        error = Ip6Address::FromString(addressStr.c_str(), address);
+
+        if (error != OTBR_ERROR_NONE)
+        {
+            otbrLogInfo("Failed to parse resolved IPv6 address: %s", addressStr.c_str());
+            continue;
+        }
+        info.mAddresses.push_back(address);
+    }
+    for (const auto &entry : aTxt)
+    {
+        txtList.emplace_back(entry.name.c_str(), entry.value.data(), entry.value.size());
+    }
+    EncodeTxtData(txtList, info.mTxtData);
+
+    mSubscription.mPublisher.OnServiceResolved(aType, info);
+
+    return Status::ok();
+}
+
 void MdnsPublisher::SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher)
 {
     otbrLogInfo("Set INsdPublisher %p", aINsdPublisher.get());
 
-    if (aINsdPublisher)
+    mNsdPublisher = std::move(aINsdPublisher);
+
+    if (mNsdPublisher != nullptr)
     {
-        mNsdPublisher = std::move(aINsdPublisher);
         mStateCallback(Mdns::Publisher::State::kReady);
     }
     else
     {
-        Stop();
-        mNsdPublisher = std::move(aINsdPublisher);
+        mStateCallback(Mdns::Publisher::State::kIdle);
     }
 }
 
@@ -92,6 +139,18 @@
     return ndk::SharedRefBase::make<MdnsPublisher::NsdStatusReceiver>(std::move(aCallback));
 }
 
+std::shared_ptr<MdnsPublisher::NsdDiscoverServiceCallback> CreateNsdDiscoverServiceCallback(
+    MdnsPublisher::ServiceSubscription &aServiceSubscription)
+{
+    return ndk::SharedRefBase::make<MdnsPublisher::NsdDiscoverServiceCallback>(aServiceSubscription);
+}
+
+std::shared_ptr<MdnsPublisher::NsdResolveServiceCallback> CreateNsdResolveServiceCallback(
+    MdnsPublisher::ServiceSubscription &aServiceSubscription)
+{
+    return ndk::SharedRefBase::make<MdnsPublisher::NsdResolveServiceCallback>(aServiceSubscription);
+}
+
 void DieForNotImplemented(const char *aFuncName)
 {
     VerifyOrDie(false, (std::string(aFuncName) + " is not implemented").c_str());
@@ -250,32 +309,62 @@
 
 void MdnsPublisher::SubscribeService(const std::string &aType, const std::string &aInstanceName)
 {
-    OTBR_UNUSED_VARIABLE(aType);
-    OTBR_UNUSED_VARIABLE(aInstanceName);
+    auto service = MakeUnique<ServiceSubscription>(aType, aInstanceName, *this, mNsdPublisher);
 
-    DieForNotImplemented(__func__);
+    VerifyOrExit(IsStarted(), otbrLogWarning("No platform mDNS implementation registered!"));
+
+    mServiceSubscriptions.push_back(std::move(service));
+
+    otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(),
+                mServiceSubscriptions.size());
+
+    if (aInstanceName.empty())
+    {
+        mServiceSubscriptions.back()->Browse();
+    }
+    else
+    {
+        mServiceSubscriptions.back()->Resolve(aInstanceName, aType);
+    }
+exit:
+    return;
 }
 
 void MdnsPublisher::UnsubscribeService(const std::string &aType, const std::string &aInstanceName)
 {
-    OTBR_UNUSED_VARIABLE(aType);
-    OTBR_UNUSED_VARIABLE(aInstanceName);
+    ServiceSubscriptionList::iterator it;
 
-    DieForNotImplemented(__func__);
+    VerifyOrExit(IsStarted());
+
+    it = std::find_if(mServiceSubscriptions.begin(), mServiceSubscriptions.end(),
+                      [&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) {
+                          return aService->mType == aType && aService->mName == aInstanceName;
+                      });
+
+    VerifyOrExit(it != mServiceSubscriptions.end(),
+                 otbrLogWarning("The service %s.%s is already unsubscribed.", aInstanceName.c_str(), aType.c_str()));
+
+    {
+        std::unique_ptr<ServiceSubscription> service = std::move(*it);
+
+        mServiceSubscriptions.erase(it);
+    }
+
+    otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(),
+                mServiceSubscriptions.size());
+
+exit:
+    return;
 }
 
 void MdnsPublisher::SubscribeHost(const std::string &aHostName)
 {
     OTBR_UNUSED_VARIABLE(aHostName);
-
-    DieForNotImplemented(__func__);
 }
 
 void MdnsPublisher::UnsubscribeHost(const std::string &aHostName)
 {
     OTBR_UNUSED_VARIABLE(aHostName);
-
-    DieForNotImplemented(__func__);
 }
 
 void MdnsPublisher::OnServiceResolveFailedImpl(const std::string &aType,
@@ -308,7 +397,9 @@
 
 MdnsPublisher::NsdServiceRegistration::~NsdServiceRegistration(void)
 {
-    VerifyOrExit(mPublisher->IsStarted() && mNsdPublisher != nullptr);
+    auto nsdPublisher = mNsdPublisher.lock();
+
+    VerifyOrExit(mPublisher->IsStarted() && nsdPublisher != nullptr);
 
     otbrLogInfo("Unpublishing service %s.%s listener ID = %d", mName.c_str(), mType.c_str(), mListenerId);
 
@@ -317,7 +408,7 @@
         mUnregisterReceiver = CreateReceiver([](int) {});
     }
 
-    mNsdPublisher->unregister(mUnregisterReceiver, mListenerId);
+    nsdPublisher->unregister(mUnregisterReceiver, mListenerId);
 
 exit:
     return;
@@ -325,7 +416,9 @@
 
 MdnsPublisher::NsdHostRegistration::~NsdHostRegistration(void)
 {
-    VerifyOrExit(mPublisher->IsStarted() && mNsdPublisher != nullptr);
+    auto nsdPublisher = mNsdPublisher.lock();
+
+    VerifyOrExit(mPublisher->IsStarted() && nsdPublisher != nullptr);
 
     otbrLogInfo("Unpublishing host %s listener ID = %d", mName.c_str(), mListenerId);
 
@@ -334,11 +427,80 @@
         mUnregisterReceiver = CreateReceiver([](int) {});
     }
 
-    mNsdPublisher->unregister(mUnregisterReceiver, mListenerId);
+    nsdPublisher->unregister(mUnregisterReceiver, mListenerId);
 
 exit:
     return;
 }
 
+void MdnsPublisher::ServiceSubscription::Release(void)
+{
+    otbrLogInfo("Browsing service type %s", mType.c_str());
+
+    std::vector<std::string> instanceNames;
+
+    for (const auto &nameAndResolvers : mResolvers)
+    {
+        instanceNames.push_back(nameAndResolvers.first);
+    }
+    for (const auto &name : instanceNames)
+    {
+        RemoveServiceResolver(name);
+    }
+
+    mNsdPublisher->stopServiceDiscovery(mBrowseListenerId);
+}
+
+void MdnsPublisher::ServiceSubscription::Browse(void)
+{
+    VerifyOrExit(mPublisher.IsStarted());
+
+    otbrLogInfo("Browsing service type %s", mType.c_str());
+
+    mNsdPublisher->discoverService(mType, CreateNsdDiscoverServiceCallback(*this), mBrowseListenerId);
+
+exit:
+    return;
+}
+
+void MdnsPublisher::ServiceSubscription::Resolve(const std::string &aName, const std::string &aType)
+{
+    ServiceResolver *resolver = new ServiceResolver(mPublisher.AllocateListenerId(), mNsdPublisher);
+
+    VerifyOrExit(mPublisher.IsStarted());
+
+    otbrLogInfo("Resolving service %s.%s", aName.c_str(), aType.c_str());
+
+    AddServiceResolver(aName, resolver);
+    mNsdPublisher->resolveService(aName, aType, CreateNsdResolveServiceCallback(*this), resolver->mListenerId);
+
+exit:
+    return;
+}
+
+void MdnsPublisher::ServiceSubscription::AddServiceResolver(const std::string &aName, ServiceResolver *aResolver)
+{
+    mResolvers[aName].insert(aResolver);
+}
+void MdnsPublisher::ServiceSubscription::RemoveServiceResolver(const std::string &aName)
+{
+    int numResolvers = 0;
+
+    VerifyOrExit(mResolvers.find(aName) != mResolvers.end());
+
+    numResolvers = mResolvers[aName].size();
+
+    for (auto resolver : mResolvers[aName])
+    {
+        delete resolver;
+    }
+
+    mResolvers.erase(aName);
+
+exit:
+    otbrLogDebug("Removed %d service resolver for instance %s", numResolvers, aName.c_str());
+    return;
+}
+
 } // namespace Android
 } // namespace otbr
diff --git a/src/android/mdns_publisher.hpp b/src/android/mdns_publisher.hpp
index a547d4f..64793dc 100644
--- a/src/android/mdns_publisher.hpp
+++ b/src/android/mdns_publisher.hpp
@@ -31,12 +31,18 @@
 
 #include "mdns/mdns.hpp"
 
+#include <aidl/com/android/server/thread/openthread/BnNsdDiscoverServiceCallback.h>
+#include <aidl/com/android/server/thread/openthread/BnNsdResolveServiceCallback.h>
 #include <aidl/com/android/server/thread/openthread/BnNsdStatusReceiver.h>
 #include <aidl/com/android/server/thread/openthread/DnsTxtAttribute.h>
 #include <aidl/com/android/server/thread/openthread/INsdPublisher.h>
+#include <set>
 
 namespace otbr {
 namespace Android {
+using Status = ::ndk::ScopedAStatus;
+using aidl::com::android::server::thread::openthread::BnNsdDiscoverServiceCallback;
+using aidl::com::android::server::thread::openthread::BnNsdResolveServiceCallback;
 using aidl::com::android::server::thread::openthread::BnNsdStatusReceiver;
 using aidl::com::android::server::thread::openthread::DnsTxtAttribute;
 using aidl::com::android::server::thread::openthread::INsdPublisher;
@@ -45,15 +51,15 @@
 {
 public:
     explicit MdnsPublisher(Publisher::StateCallback aCallback)
+        : mStateCallback(std::move(aCallback))
+        , mNextListenerId(0)
+
     {
-        mNextListenerId = 0;
-        mStateCallback  = std::move(aCallback);
     }
 
     ~MdnsPublisher(void) { Stop(); }
 
-    // In this Publisher implementation, SetINsdPublisher() does the job to start/stop the Publisher. That's because we
-    // want to ensure ot-daemon won't do any mDNS operations when Thread is disabled.
+    /** Sets the INsdPublisher which forwards the mDNS API requests to the NsdManager in system_server. */
     void SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher);
 
     otbrError Start(void) override { return OTBR_ERROR_NONE; }
@@ -62,7 +68,10 @@
     {
         mServiceRegistrations.clear();
         mHostRegistrations.clear();
-        mStateCallback(Mdns::Publisher::State::kIdle);
+        if (mNsdPublisher != nullptr)
+        {
+            mNsdPublisher->reset();
+        }
     }
 
     bool IsStarted(void) const override { return mNsdPublisher != nullptr; }
@@ -89,14 +98,99 @@
         {
         }
 
-        ::ndk::ScopedAStatus onSuccess(void) override;
+        Status onSuccess(void) override;
 
-        ::ndk::ScopedAStatus onError(int aError) override;
+        Status onError(int aError) override;
 
     private:
         Mdns::Publisher::ResultCallback mCallback;
     };
 
+    struct ServiceResolver : private ::NonCopyable
+    {
+        explicit ServiceResolver(int aListenerId, std::shared_ptr<INsdPublisher> aNsdPublisher)
+            : mListenerId(aListenerId)
+            , mNsdPublisher(std::move(aNsdPublisher))
+        {
+        }
+
+        ~ServiceResolver(void)
+        {
+            if (mNsdPublisher)
+            {
+                mNsdPublisher->stopServiceResolution(mListenerId);
+            }
+        }
+
+        int                            mListenerId;
+        std::shared_ptr<INsdPublisher> mNsdPublisher;
+    };
+
+    struct ServiceSubscription : private ::NonCopyable
+    {
+        explicit ServiceSubscription(std::string                    aType,
+                                     std::string                    aName,
+                                     MdnsPublisher                 &aPublisher,
+                                     std::shared_ptr<INsdPublisher> aNsdPublisher)
+            : mType(std::move(aType))
+            , mName(std::move(aName))
+            , mPublisher(aPublisher)
+            , mNsdPublisher(std::move(aNsdPublisher))
+            , mBrowseListenerId(-1)
+        {
+        }
+
+        ~ServiceSubscription(void) { Release(); }
+
+        void Release(void);
+        void Browse(void);
+        void Resolve(const std::string &aName, const std::string &aType);
+        void AddServiceResolver(const std::string &aName, ServiceResolver *aResolver);
+        void RemoveServiceResolver(const std::string &aInstanceName);
+
+        std::string                    mType;
+        std::string                    mName;
+        MdnsPublisher                 &mPublisher;
+        std::shared_ptr<INsdPublisher> mNsdPublisher;
+        int32_t                        mBrowseListenerId;
+
+        std::map<std::string, std::set<ServiceResolver *>> mResolvers;
+    };
+
+    class NsdDiscoverServiceCallback : public BnNsdDiscoverServiceCallback
+    {
+    public:
+        explicit NsdDiscoverServiceCallback(ServiceSubscription &aSubscription)
+            : mSubscription(aSubscription)
+        {
+        }
+
+        Status onServiceDiscovered(const std::string &aName, const std::string &aType, bool aIsFound);
+
+    private:
+        ServiceSubscription &mSubscription;
+    };
+
+    class NsdResolveServiceCallback : public BnNsdResolveServiceCallback
+    {
+    public:
+        explicit NsdResolveServiceCallback(ServiceSubscription &aSubscription)
+            : mSubscription(aSubscription)
+        {
+        }
+
+        Status onServiceResolved(const std::string                  &aHostname,
+                                 const std::string                  &aName,
+                                 const std::string                  &aType,
+                                 int                                 aPort,
+                                 const std::vector<std::string>     &aAddresses,
+                                 const std::vector<DnsTxtAttribute> &aTxt,
+                                 int                                 aTtlSeconds);
+
+    private:
+        ServiceSubscription &mSubscription;
+    };
+
 protected:
     otbrError PublishServiceImpl(const std::string &aHostName,
                                  const std::string &aName,
@@ -120,16 +214,16 @@
     class NsdServiceRegistration : public ServiceRegistration
     {
     public:
-        NsdServiceRegistration(const std::string             &aHostName,
-                               const std::string             &aName,
-                               const std::string             &aType,
-                               const SubTypeList             &aSubTypeList,
-                               uint16_t                       aPort,
-                               const TxtData                 &aTxtData,
-                               ResultCallback               &&aCallback,
-                               MdnsPublisher                 *aPublisher,
-                               int32_t                        aListenerId,
-                               std::shared_ptr<INsdPublisher> aINsdPublisher)
+        NsdServiceRegistration(const std::string           &aHostName,
+                               const std::string           &aName,
+                               const std::string           &aType,
+                               const SubTypeList           &aSubTypeList,
+                               uint16_t                     aPort,
+                               const TxtData               &aTxtData,
+                               ResultCallback             &&aCallback,
+                               MdnsPublisher               *aPublisher,
+                               int32_t                      aListenerId,
+                               std::weak_ptr<INsdPublisher> aINsdPublisher)
             : ServiceRegistration(aHostName,
                                   aName,
                                   aType,
@@ -150,18 +244,18 @@
         std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver;
 
     private:
-        std::shared_ptr<INsdPublisher> mNsdPublisher;
+        std::weak_ptr<INsdPublisher> mNsdPublisher;
     };
 
     class NsdHostRegistration : public HostRegistration
     {
     public:
-        NsdHostRegistration(const std::string             &aName,
-                            const AddressList             &aAddresses,
-                            ResultCallback               &&aCallback,
-                            MdnsPublisher                 *aPublisher,
-                            int32_t                        aListenerId,
-                            std::shared_ptr<INsdPublisher> aINsdPublisher)
+        NsdHostRegistration(const std::string           &aName,
+                            const AddressList           &aAddresses,
+                            ResultCallback             &&aCallback,
+                            MdnsPublisher               *aPublisher,
+                            int32_t                      aListenerId,
+                            std::weak_ptr<INsdPublisher> aINsdPublisher)
             : HostRegistration(aName, aAddresses, std::move(aCallback), aPublisher)
             , mListenerId(aListenerId)
             , mNsdPublisher(aINsdPublisher)
@@ -174,14 +268,20 @@
         std::shared_ptr<NsdStatusReceiver> mUnregisterReceiver;
 
     private:
-        std::shared_ptr<INsdPublisher> mNsdPublisher;
+        std::weak_ptr<INsdPublisher> mNsdPublisher;
     };
 
+    typedef std::vector<std::unique_ptr<ServiceSubscription>> ServiceSubscriptionList;
+
+    static constexpr int kMinResolvedTtl = 1;
+    static constexpr int kMaxResolvedTtl = 10;
+
     int32_t AllocateListenerId(void);
 
     StateCallback                  mStateCallback;
     int32_t                        mNextListenerId;
     std::shared_ptr<INsdPublisher> mNsdPublisher = nullptr;
+    ServiceSubscriptionList        mServiceSubscriptions;
 };
 
 } // namespace Android
diff --git a/src/android/otdaemon_fuzzer.cpp b/src/android/otdaemon_fuzzer.cpp
index be0bc34..9eb3235 100644
--- a/src/android/otdaemon_fuzzer.cpp
+++ b/src/android/otdaemon_fuzzer.cpp
@@ -41,3 +41,7 @@
     fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size));
     return 0;
 }
+
+extern "C" void otPlatReset(otInstance *aInstance)
+{
+}
diff --git a/src/android/otdaemon_server.cpp b/src/android/otdaemon_server.cpp
index 0c6930b..9ffeb7b 100644
--- a/src/android/otdaemon_server.cpp
+++ b/src/android/otdaemon_server.cpp
@@ -34,14 +34,17 @@
 #include <string.h>
 
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 #include <openthread/border_router.h>
+#include <openthread/cli.h>
 #include <openthread/icmp6.h>
 #include <openthread/ip6.h>
 #include <openthread/link.h>
 #include <openthread/openthread-system.h>
 #include <openthread/platform/infra_if.h>
+#include <openthread/platform/radio.h>
 
 #include "agent/vendor.hpp"
 #include "android/otdaemon_telemetry.hpp"
@@ -97,8 +100,25 @@
     return addrInfo;
 }
 
+static const char *ThreadEnabledStateToString(int enabledState)
+{
+    switch (enabledState)
+    {
+    case IOtDaemon::OT_STATE_ENABLED:
+        return "ENABLED";
+    case IOtDaemon::OT_STATE_DISABLED:
+        return "DISABLED";
+    case IOtDaemon::OT_STATE_DISABLING:
+        return "DISABLING";
+    default:
+        assert(false);
+        return "UNKNOWN";
+    }
+}
+
 OtDaemonServer::OtDaemonServer(Application &aApplication)
-    : mNcp(aApplication.GetNcp())
+    : mApplication(aApplication)
+    , mNcp(aApplication.GetNcp())
     , mBorderAgent(aApplication.GetBorderAgent())
     , mMdnsPublisher(static_cast<MdnsPublisher &>(aApplication.GetPublisher()))
     , mBorderRouterConfiguration()
@@ -131,7 +151,14 @@
 {
     OtDaemonServer *thisServer = static_cast<OtDaemonServer *>(aBinderServer);
 
-    otbrLogCrit("Client is died, removing callbacks...");
+    otbrLogCrit("system_server is dead, removing configs and callbacks...");
+
+    thisServer->mMeshcopTxts   = {};
+    thisServer->mINsdPublisher = nullptr;
+
+    // Note that the INsdPublisher reference is held in MdnsPublisher
+    thisServer->mMdnsPublisher.SetINsdPublisher(nullptr);
+
     thisServer->mCallback = nullptr;
     thisServer->mTunFd.set(-1); // the original FD will be closed automatically
 }
@@ -243,8 +270,9 @@
 
     message = otIp6NewMessage(GetOtInstance(), &settings);
     VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
+    otMessageSetOrigin(message, OT_MESSAGE_ORIGIN_HOST_UNTRUSTED);
 
-    SuccessOrExit(error = otMessageAppend(message, packet, length));
+    SuccessOrExit(error = otMessageAppend(message, packet, static_cast<uint16_t>(length)));
 
     error   = otIp6Send(GetOtInstance(), message);
     message = nullptr;
@@ -352,9 +380,12 @@
     }
 }
 
-Status OtDaemonServer::initialize(const ScopedFileDescriptor           &aTunFd,
-                                  const bool                            enabled,
-                                  const std::shared_ptr<INsdPublisher> &aINsdPublisher)
+Status OtDaemonServer::initialize(const ScopedFileDescriptor               &aTunFd,
+                                  const bool                                enabled,
+                                  const std::shared_ptr<INsdPublisher>     &aINsdPublisher,
+                                  const MeshcopTxtAttributes               &aMeshcopTxts,
+                                  const std::shared_ptr<IOtDaemonCallback> &aCallback,
+                                  const std::string                        &aCountryCode)
 {
     otbrLogInfo("OT daemon is initialized by system server (tunFd=%d, enabled=%s)", aTunFd.get(),
                 enabled ? "true" : "false");
@@ -363,14 +394,31 @@
     // we can process `aTunFd` directly in front of the task.
     mTunFd = aTunFd.dup();
 
-    mTaskRunner.Post([enabled, aINsdPublisher, this]() { initializeInternal(enabled, aINsdPublisher); });
+    mINsdPublisher = aINsdPublisher;
+    mMeshcopTxts   = aMeshcopTxts;
+
+    mTaskRunner.Post([enabled, aINsdPublisher, aMeshcopTxts, aCallback, aCountryCode, this]() {
+        initializeInternal(enabled, mINsdPublisher, mMeshcopTxts, aCallback, aCountryCode);
+    });
 
     return Status::ok();
 }
 
-void OtDaemonServer::initializeInternal(const bool enabled, const std::shared_ptr<INsdPublisher> &aINsdPublisher)
+void OtDaemonServer::initializeInternal(const bool                                enabled,
+                                        const std::shared_ptr<INsdPublisher>     &aINsdPublisher,
+                                        const MeshcopTxtAttributes               &aMeshcopTxts,
+                                        const std::shared_ptr<IOtDaemonCallback> &aCallback,
+                                        const std::string                        &aCountryCode)
 {
-    mINsdPublisher = aINsdPublisher;
+    std::string instanceName = aMeshcopTxts.vendorName + " " + aMeshcopTxts.modelName;
+
+    setCountryCodeInternal(aCountryCode, nullptr /* aReceiver */);
+    registerStateCallbackInternal(aCallback, -1 /* listenerId */);
+
+    mMdnsPublisher.SetINsdPublisher(aINsdPublisher);
+    mBorderAgent.SetMeshCopServiceValues(instanceName, aMeshcopTxts.modelName, aMeshcopTxts.vendorName,
+                                         aMeshcopTxts.vendorOui);
+    mBorderAgent.SetEnabled(enabled);
 
     if (enabled)
     {
@@ -378,27 +426,41 @@
     }
     else
     {
-        updateThreadEnabledState(enabled, nullptr /* aReceiver */);
+        updateThreadEnabledState(OT_STATE_DISABLED, nullptr /* aReceiver */);
     }
 }
 
+Status OtDaemonServer::terminate(void)
+{
+    mTaskRunner.Post([]() {
+        otbrLogWarning("Terminating ot-daemon process...");
+        exit(0);
+    });
+    return Status::ok();
+}
+
 void OtDaemonServer::updateThreadEnabledState(const int enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver)
 {
+    VerifyOrExit(enabled != mThreadEnabled);
+
+    otbrLogInfo("Thread enabled state changed: %s -> %s", ThreadEnabledStateToString(mThreadEnabled),
+                ThreadEnabledStateToString(enabled));
     mThreadEnabled = enabled;
+
     if (aReceiver != nullptr)
     {
         aReceiver->onSuccess();
     }
 
+    // Enables the BorderAgent module only when Thread is enabled because it always
+    // publishes the MeshCoP service even when no Thread network is provisioned.
     switch (enabled)
     {
     case OT_STATE_ENABLED:
-        mMdnsPublisher.SetINsdPublisher(mINsdPublisher);
+        mBorderAgent.SetEnabled(true);
         break;
     case OT_STATE_DISABLED:
-        mMdnsPublisher.SetINsdPublisher(nullptr);
-        break;
-    default:
+        mBorderAgent.SetEnabled(false);
         break;
     }
 
@@ -406,6 +468,9 @@
     {
         mCallback->onThreadEnabledChanged(mThreadEnabled);
     }
+
+exit:
+    return;
 }
 
 void OtDaemonServer::enableThread(const std::shared_ptr<IOtStatusReceiver> &aReceiver)
@@ -418,7 +483,7 @@
         (void)otIp6SetEnabled(GetOtInstance(), true);
         (void)otThreadSetEnabled(GetOtInstance(), true);
     }
-    updateThreadEnabledState(IOtDaemon::OT_STATE_ENABLED, aReceiver);
+    updateThreadEnabledState(OT_STATE_ENABLED, aReceiver);
 }
 
 Status OtDaemonServer::setThreadEnabled(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver)
@@ -435,10 +500,9 @@
 
     VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized");
 
-    VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY,
-                 message = "Thread is disabling");
+    VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling");
 
-    if ((mThreadEnabled == IOtDaemon::OT_STATE_ENABLED) == enabled)
+    if ((mThreadEnabled == OT_STATE_ENABLED) == enabled)
     {
         aReceiver->onSuccess();
         ExitNow();
@@ -450,17 +514,14 @@
     }
     else
     {
-        mThreadEnabled = IOtDaemon::OT_STATE_DISABLING;
-        if (mCallback != nullptr)
-        {
-            mCallback->onThreadEnabledChanged(mThreadEnabled);
-        }
+        // `aReceiver` should not be set here because the operation isn't finished yet
+        updateThreadEnabledState(OT_STATE_DISABLING, nullptr /* aReceiver */);
 
         LeaveGracefully([aReceiver, this]() {
             // Ignore errors as those operations should always succeed
             (void)otThreadSetEnabled(GetOtInstance(), false);
             (void)otIp6SetEnabled(GetOtInstance(), false);
-            updateThreadEnabledState(IOtDaemon::OT_STATE_DISABLED, aReceiver);
+            updateThreadEnabledState(OT_STATE_DISABLED, aReceiver);
         });
     }
 
@@ -552,6 +613,7 @@
 
     if (isAttached() && !mState.activeDatasetTlvs.empty() && mJoinReceiver != nullptr)
     {
+        otbrLogInfo("Join succeeded");
         mJoinReceiver->onSuccess();
         mJoinReceiver = nullptr;
     }
@@ -574,10 +636,9 @@
     std::string              message;
     otOperationalDatasetTlvs datasetTlvs;
 
-    VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY,
-                 message = "Thread is disabling");
+    VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling");
 
-    VerifyOrExit(mThreadEnabled == IOtDaemon::OT_STATE_ENABLED,
+    VerifyOrExit(mThreadEnabled == OT_STATE_ENABLED,
                  error   = static_cast<int>(IOtDaemon::ErrorCode::OT_ERROR_THREAD_DISABLED),
                  message = "Thread is disabled");
 
@@ -587,12 +648,15 @@
 
     if (otThreadGetDeviceRole(GetOtInstance()) != OT_DEVICE_ROLE_DISABLED)
     {
-        LeaveGracefully([aActiveOpDatasetTlvs, aReceiver, this]() { join(aActiveOpDatasetTlvs, aReceiver); });
+        LeaveGracefully([aActiveOpDatasetTlvs, aReceiver, this]() {
+            FinishLeave(nullptr);
+            join(aActiveOpDatasetTlvs, aReceiver);
+        });
         ExitNow();
     }
 
     std::copy(aActiveOpDatasetTlvs.begin(), aActiveOpDatasetTlvs.end(), datasetTlvs.mTlvs);
-    datasetTlvs.mLength = aActiveOpDatasetTlvs.size();
+    datasetTlvs.mLength = static_cast<uint8_t>(aActiveOpDatasetTlvs.size());
     SuccessOrExit(error   = otDatasetSetActiveTlvs(GetOtInstance(), &datasetTlvs),
                   message = "Failed to set Active Operational Dataset");
 
@@ -630,20 +694,15 @@
 
     VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized");
 
-    VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY,
-                 message = "Thread is disabling");
+    VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling");
 
-    if (mThreadEnabled == IOtDaemon::OT_STATE_DISABLED)
+    if (mThreadEnabled == OT_STATE_DISABLED)
     {
-        (void)otInstanceErasePersistentInfo(GetOtInstance());
-        aReceiver->onSuccess();
+        FinishLeave(aReceiver);
         ExitNow();
     }
 
-    LeaveGracefully([aReceiver, this]() {
-        (void)otInstanceErasePersistentInfo(GetOtInstance());
-        aReceiver->onSuccess();
-    });
+    LeaveGracefully([aReceiver, this]() { FinishLeave(aReceiver); });
 
 exit:
     if (error != OT_ERROR_NONE)
@@ -652,6 +711,17 @@
     }
 }
 
+void OtDaemonServer::FinishLeave(const std::shared_ptr<IOtStatusReceiver> &aReceiver)
+{
+    (void)otInstanceErasePersistentInfo(GetOtInstance());
+    OT_UNUSED_VARIABLE(mApplication); // Avoid the unused-private-field issue.
+    // TODO: b/323301831 - Re-init the Application class.
+    if (aReceiver != nullptr)
+    {
+        aReceiver->onSuccess();
+    }
+}
+
 void OtDaemonServer::LeaveGracefully(const LeaveCallback &aReceiver)
 {
     mLeaveCallbacks.push_back(aReceiver);
@@ -712,10 +782,9 @@
     std::string          message;
     otOperationalDataset emptyDataset;
 
-    VerifyOrExit(mThreadEnabled != IOtDaemon::OT_STATE_DISABLING, error = OT_ERROR_BUSY,
-                 message = "Thread is disabling");
+    VerifyOrExit(mThreadEnabled != OT_STATE_DISABLING, error = OT_ERROR_BUSY, message = "Thread is disabling");
 
-    VerifyOrExit(mThreadEnabled == IOtDaemon::OT_STATE_ENABLED,
+    VerifyOrExit(mThreadEnabled == OT_STATE_ENABLED,
                  error   = static_cast<int>(IOtDaemon::ErrorCode::OT_ERROR_THREAD_DISABLED),
                  message = "Thread is disabled");
 
@@ -733,7 +802,7 @@
     // TODO: check supported channel mask
 
     error = otDatasetSendMgmtPendingSet(GetOtInstance(), &emptyDataset, aPendingOpDatasetTlvs.data(),
-                                        aPendingOpDatasetTlvs.size(), SendMgmtPendingSetCallback,
+                                        static_cast<uint8_t>(aPendingOpDatasetTlvs.size()), SendMgmtPendingSetCallback,
                                         /* aBinderServer= */ this);
     if (error != OT_ERROR_NONE)
     {
@@ -787,7 +856,7 @@
     otbrLogInfo("Set country code: %c%c", aCountryCode[0], aCountryCode[1]);
     VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized");
 
-    countryCode = (static_cast<uint16_t>(aCountryCode[0]) << 8) | aCountryCode[1];
+    countryCode = static_cast<uint16_t>((aCountryCode[0] << 8) | aCountryCode[1]);
     SuccessOrExit(error = otLinkSetRegion(GetOtInstance(), countryCode), message = "Failed to set the country code");
 
 exit:
@@ -826,6 +895,48 @@
     }
 }
 
+Status OtDaemonServer::setChannelMaxPowers(const std::vector<ChannelMaxPower>       &aChannelMaxPowers,
+                                           const std::shared_ptr<IOtStatusReceiver> &aReceiver)
+{
+    mTaskRunner.Post(
+        [aChannelMaxPowers, aReceiver, this]() { setChannelMaxPowersInternal(aChannelMaxPowers, aReceiver); });
+
+    return Status::ok();
+}
+
+Status OtDaemonServer::setChannelMaxPowersInternal(const std::vector<ChannelMaxPower>       &aChannelMaxPowers,
+                                                   const std::shared_ptr<IOtStatusReceiver> &aReceiver)
+{
+    otError     error = OT_ERROR_NONE;
+    std::string message;
+    uint8_t     channel;
+    int16_t     maxPower;
+
+    VerifyOrExit(GetOtInstance() != nullptr, error = OT_ERROR_INVALID_STATE, message = "OT is not initialized");
+
+    for (ChannelMaxPower channelMaxPower : aChannelMaxPowers)
+    {
+        VerifyOrExit((channelMaxPower.channel >= OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN) &&
+                         (channelMaxPower.channel <= OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX),
+                     error = OT_ERROR_INVALID_ARGS, message = "The channel is invalid");
+        VerifyOrExit((channelMaxPower.maxPower >= INT16_MIN) && (channelMaxPower.maxPower <= INT16_MAX),
+                     error = OT_ERROR_INVALID_ARGS, message = "The max power is invalid");
+    }
+
+    for (ChannelMaxPower channelMaxPower : aChannelMaxPowers)
+    {
+        channel  = static_cast<uint8_t>(channelMaxPower.channel);
+        maxPower = static_cast<int16_t>(channelMaxPower.maxPower);
+        otbrLogInfo("Set channel max power: channel=%u, maxPower=%d", channel, maxPower);
+        SuccessOrExit(error   = otPlatRadioSetChannelTargetPower(GetOtInstance(), channel, maxPower),
+                      message = "Failed to set channel max power");
+    }
+
+exit:
+    PropagateResult(error, message, aReceiver);
+    return Status::ok();
+}
+
 Status OtDaemonServer::configureBorderRouter(const BorderRouterConfigurationParcel    &aBorderRouterConfiguration,
                                              const std::shared_ptr<IOtStatusReceiver> &aReceiver)
 {
@@ -864,7 +975,7 @@
     {
         if (aIsBorderRoutingEnabled)
         {
-            int infraIfIndex = if_nametoindex(aInfraInterfaceName.c_str());
+            unsigned int infraIfIndex = if_nametoindex(aInfraInterfaceName.c_str());
             SuccessOrExit(error   = otBorderRoutingSetEnabled(GetOtInstance(), false /* aEnabled */),
                           message = "failed to disable border routing");
             otSysSetInfraNetif(aInfraInterfaceName.c_str(), icmp6SocketFd);
@@ -895,14 +1006,52 @@
     PropagateResult(error, message, aReceiver);
 }
 
+static int OutputCallback(void *aContext, const char *aFormat, va_list aArguments)
+{
+    std::string output;
+
+    android::base::StringAppendV(&output, aFormat, aArguments);
+
+    int length = output.length();
+
+    VerifyOrExit(android::base::WriteStringToFd(output, *(static_cast<int *>(aContext))), length = 0);
+
+exit:
+    return length;
+}
+
+inline void DumpCliCommand(std::string aCommand, int aFd)
+{
+    android::base::WriteStringToFd(aCommand + '\n', aFd);
+    otCliInputLine(aCommand.data());
+}
+
 binder_status_t OtDaemonServer::dump(int aFd, const char **aArgs, uint32_t aNumArgs)
 {
     OT_UNUSED_VARIABLE(aArgs);
     OT_UNUSED_VARIABLE(aNumArgs);
 
-    // TODO: Use ::android::base::WriteStringToFd to dump infomration.
+    otCliInit(GetOtInstance(), OutputCallback, &aFd);
+
+    DumpCliCommand("state", aFd);
+    DumpCliCommand("srp server state", aFd);
+    DumpCliCommand("srp server service", aFd);
+    DumpCliCommand("srp server host", aFd);
+    DumpCliCommand("dataset active", aFd);
+    DumpCliCommand("leaderdata", aFd);
+    DumpCliCommand("eidcache", aFd);
+    DumpCliCommand("counters mac", aFd);
+    DumpCliCommand("counters mle", aFd);
+    DumpCliCommand("counters ip", aFd);
+    DumpCliCommand("router table", aFd);
+    DumpCliCommand("neighbor table", aFd);
+    DumpCliCommand("ipaddr -v", aFd);
+    DumpCliCommand("netdata show", aFd);
+
     fsync(aFd);
 
+    otSysCliInitUsingDaemon(GetOtInstance());
+
     return STATUS_OK;
 }
 
diff --git a/src/android/otdaemon_server.hpp b/src/android/otdaemon_server.hpp
index a61d19d..8124952 100644
--- a/src/android/otdaemon_server.hpp
+++ b/src/android/otdaemon_server.hpp
@@ -51,6 +51,7 @@
 using BinderDeathRecipient = ::ndk::ScopedAIBinder_DeathRecipient;
 using ScopedFileDescriptor = ::ndk::ScopedFileDescriptor;
 using Status               = ::ndk::ScopedAStatus;
+using aidl::android::net::thread::ChannelMaxPower;
 using aidl::com::android::server::thread::openthread::BackboneRouterState;
 using aidl::com::android::server::thread::openthread::BnOtDaemon;
 using aidl::com::android::server::thread::openthread::BorderRouterConfigurationParcel;
@@ -60,6 +61,7 @@
 using aidl::com::android::server::thread::openthread::IOtDaemonCallback;
 using aidl::com::android::server::thread::openthread::IOtStatusReceiver;
 using aidl::com::android::server::thread::openthread::Ipv6AddressInfo;
+using aidl::com::android::server::thread::openthread::MeshcopTxtAttributes;
 using aidl::com::android::server::thread::openthread::OtDaemonState;
 
 class OtDaemonServer : public BnOtDaemon, public MainloopProcessor, public vendor::VendorServer
@@ -91,10 +93,18 @@
 
     // Implements IOtDaemon.aidl
 
-    Status initialize(const ScopedFileDescriptor           &aTunFd,
-                      const bool                            enabled,
-                      const std::shared_ptr<INsdPublisher> &aNsdPublisher) override;
-    void   initializeInternal(const bool enabled, const std::shared_ptr<INsdPublisher> &aINsdPublisher);
+    Status initialize(const ScopedFileDescriptor               &aTunFd,
+                      const bool                                enabled,
+                      const std::shared_ptr<INsdPublisher>     &aNsdPublisher,
+                      const MeshcopTxtAttributes               &aMeshcopTxts,
+                      const std::shared_ptr<IOtDaemonCallback> &aCallback,
+                      const std::string                        &aCountryCode) override;
+    void   initializeInternal(const bool                                enabled,
+                              const std::shared_ptr<INsdPublisher>     &aINsdPublisher,
+                              const MeshcopTxtAttributes               &aMeshcopTxts,
+                              const std::shared_ptr<IOtDaemonCallback> &aCallback,
+                              const std::string                        &aCountryCode);
+    Status terminate(void) override;
     Status setThreadEnabled(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver) override;
     void   setThreadEnabledInternal(const bool enabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     Status registerStateCallback(const std::shared_ptr<IOtDaemonCallback> &aCallback, int64_t listenerId) override;
@@ -112,6 +122,10 @@
                                      const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     Status setCountryCode(const std::string &aCountryCode, const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     void   setCountryCodeInternal(const std::string &aCountryCode, const std::shared_ptr<IOtStatusReceiver> &aReceiver);
+    Status setChannelMaxPowers(const std::vector<ChannelMaxPower>       &aChannelMaxPowers,
+                               const std::shared_ptr<IOtStatusReceiver> &aReceiver);
+    Status setChannelMaxPowersInternal(const std::vector<ChannelMaxPower>       &aChannelMaxPowers,
+                                       const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     Status configureBorderRouter(const BorderRouterConfigurationParcel    &aBorderRouterConfiguration,
                                  const std::shared_ptr<IOtStatusReceiver> &aReceiver) override;
     void   configureBorderRouterInternal(int                                       aIcmp6SocketFd,
@@ -124,6 +138,7 @@
 
     bool        RefreshOtDaemonState(otChangedFlags aFlags);
     void        LeaveGracefully(const LeaveCallback &aReceiver);
+    void        FinishLeave(const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     static void DetachGracefullyCallback(void *aBinderServer);
     void        DetachGracefullyCallback(void);
     static void SendMgmtPendingSetCallback(otError aResult, void *aBinderServer);
@@ -142,11 +157,13 @@
     void updateThreadEnabledState(const int aEnabled, const std::shared_ptr<IOtStatusReceiver> &aReceiver);
     void enableThread(const std::shared_ptr<IOtStatusReceiver> &aReceiver);
 
-    int                                mThreadEnabled = IOtDaemon::OT_STATE_DISABLED;
+    int                                mThreadEnabled = OT_STATE_DISABLED;
+    otbr::Application                 &mApplication;
     otbr::Ncp::ControllerOpenThread   &mNcp;
     otbr::BorderAgent                 &mBorderAgent;
     MdnsPublisher                     &mMdnsPublisher;
     std::shared_ptr<INsdPublisher>     mINsdPublisher;
+    MeshcopTxtAttributes               mMeshcopTxts;
     TaskRunner                         mTaskRunner;
     ScopedFileDescriptor               mTunFd;
     OtDaemonState                      mState;
@@ -156,7 +173,7 @@
     std::shared_ptr<IOtStatusReceiver> mMigrationReceiver;
     std::vector<LeaveCallback>         mLeaveCallbacks;
     BorderRouterConfigurationParcel    mBorderRouterConfiguration;
-    static constexpr Seconds           kTelemetryCheckInterval           = Seconds(30);           // 30 seconds
+    static constexpr Seconds           kTelemetryCheckInterval           = Seconds(600);          // 600 seconds
     static constexpr Seconds           kTelemetryUploadIntervalThreshold = Seconds(60 * 60 * 12); // 12 hours
 };
 
diff --git a/src/border_agent/border_agent.cpp b/src/border_agent/border_agent.cpp
index acdb92d..b6acf18 100644
--- a/src/border_agent/border_agent.cpp
+++ b/src/border_agent/border_agent.cpp
@@ -69,11 +69,7 @@
 
 namespace otbr {
 
-static const char kVendorName[]             = OTBR_VENDOR_NAME;
-static const char kProductName[]            = OTBR_PRODUCT_NAME;
-static const char kBorderAgentServiceType[] = "_meshcop._udp"; ///< Border agent service type of mDNS
-static const char kBorderAgentServiceInstanceName[] =
-    OTBR_MESHCOP_SERVICE_INSTANCE_NAME; ///< Border agent service name of mDNS
+static const char    kBorderAgentServiceType[]    = "_meshcop._udp"; ///< Border agent service type of mDNS
 static constexpr int kBorderAgentServiceDummyPort = 49152;
 
 /**
@@ -143,7 +139,32 @@
     : mNcp(aNcp)
     , mPublisher(aPublisher)
     , mIsEnabled(false)
+    , mVendorName(OTBR_VENDOR_NAME)
+    , mProductName(OTBR_PRODUCT_NAME)
+    , mBaseServiceInstanceName(OTBR_MESHCOP_SERVICE_INSTANCE_NAME)
 {
+    mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); });
+}
+
+otbrError BorderAgent::SetMeshCopServiceValues(const std::string          &aServiceInstanceName,
+                                               const std::string          &aProductName,
+                                               const std::string          &aVendorName,
+                                               const std::vector<uint8_t> &aVendorOui)
+{
+    otbrError error = OTBR_ERROR_NONE;
+
+    VerifyOrExit(aProductName.size() <= kMaxProductNameLength, error = OTBR_ERROR_INVALID_ARGS);
+    VerifyOrExit(aVendorName.size() <= kMaxVendorNameLength, error = OTBR_ERROR_INVALID_ARGS);
+    VerifyOrExit(aVendorOui.empty() || aVendorOui.size() == kVendorOuiLength, error = OTBR_ERROR_INVALID_ARGS);
+
+    mProductName = aProductName;
+    mVendorName  = aVendorName;
+    mVendorOui   = aVendorOui;
+
+    mBaseServiceInstanceName = aServiceInstanceName;
+
+exit:
+    return error;
 }
 
 void BorderAgent::SetEnabled(bool aIsEnabled)
@@ -166,8 +187,6 @@
 {
     otbrLogInfo("Start Thread Border Agent");
 
-    mNcp.AddThreadStateChangedCallback([this](otChangedFlags aFlags) { HandleThreadStateChanged(aFlags); });
-
 #if OTBR_ENABLE_DBUS_SERVER
     mNcp.GetThreadHelper()->SetUpdateMeshCopTxtHandler([this](std::map<std::string, std::vector<uint8_t>> aUpdate) {
         HandleUpdateVendorMeshCoPTxtEntries(std::move(aUpdate));
@@ -179,7 +198,7 @@
     });
 #endif
 
-    mServiceInstanceName = BaseServiceInstanceName();
+    mServiceInstanceName = GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName);
     UpdateMeshCopService();
 }
 
@@ -360,8 +379,18 @@
     }
 #endif
 
-    txtList.emplace_back("vn", kVendorName);
-    txtList.emplace_back("mn", kProductName);
+    if (!mVendorOui.empty())
+    {
+        txtList.emplace_back("vo", mVendorOui.data(), mVendorOui.size());
+    }
+    if (!mVendorName.empty())
+    {
+        txtList.emplace_back("vn", mVendorName.c_str());
+    }
+    if (!mProductName.empty())
+    {
+        txtList.emplace_back("mn", mProductName.c_str());
+    }
     txtList.emplace_back("nn", networkName);
     txtList.emplace_back("xp", extPanId->m8, sizeof(extPanId->m8));
     txtList.emplace_back("tv", mNcp.GetThreadVersion());
@@ -465,6 +494,8 @@
 
 void BorderAgent::HandleThreadStateChanged(otChangedFlags aFlags)
 {
+    VerifyOrExit(IsEnabled());
+
     if (aFlags & OT_CHANGED_THREAD_ROLE)
     {
         otbrLogInfo("Thread is %s", (IsThreadStarted() ? "up" : "down"));
@@ -475,6 +506,9 @@
     {
         UpdateMeshCopService();
     }
+
+exit:
+    return;
 }
 
 bool BorderAgent::IsThreadStarted(void) const
@@ -484,12 +518,12 @@
     return role == OT_DEVICE_ROLE_CHILD || role == OT_DEVICE_ROLE_ROUTER || role == OT_DEVICE_ROLE_LEADER;
 }
 
-std::string BorderAgent::BaseServiceInstanceName() const
+std::string BorderAgent::GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const
 {
     const otExtAddress *extAddress = otLinkGetExtendedAddress(mNcp.GetInstance());
     std::stringstream   ss;
 
-    ss << kBorderAgentServiceInstanceName << " #";
+    ss << aServiceInstanceName << " #";
     ss << std::uppercase << std::hex << std::setfill('0');
     ss << std::setw(2) << static_cast<int>(extAddress->m8[6]);
     ss << std::setw(2) << static_cast<int>(extAddress->m8[7]);
@@ -504,7 +538,7 @@
     uint16_t                                rand = uniform_dist(engine);
     std::stringstream                       ss;
 
-    ss << BaseServiceInstanceName() << " (" << rand << ")";
+    ss << GetServiceInstanceNameWithExtAddr(mBaseServiceInstanceName) << " (" << rand << ")";
     return ss.str();
 }
 
diff --git a/src/border_agent/border_agent.hpp b/src/border_agent/border_agent.hpp
index c95dc3c..39a0d2c 100644
--- a/src/border_agent/border_agent.hpp
+++ b/src/border_agent/border_agent.hpp
@@ -62,7 +62,7 @@
 #endif
 
 #ifndef OTBR_MESHCOP_SERVICE_INSTANCE_NAME
-#define OTBR_MESHCOP_SERVICE_INSTANCE_NAME OTBR_VENDOR_NAME " " OTBR_PRODUCT_NAME
+#define OTBR_MESHCOP_SERVICE_INSTANCE_NAME (OTBR_VENDOR_NAME " " OTBR_PRODUCT_NAME)
 #endif
 
 namespace otbr {
@@ -95,6 +95,28 @@
     ~BorderAgent(void) = default;
 
     /**
+     * Overrides MeshCoP service (i.e. _meshcop._udp) instance name, product name, vendor name and vendor OUI.
+     *
+     * This method must be called before this BorderAgent is enabled by SetEnabled.
+     *
+     * @param[in] aServiceInstanceName  The service instance name; suffix may be appended to this value to avoid
+     *                                  name conflicts.
+     * @param[in] aProductName          The product name; must not exceed length of kMaxProductNameLength
+     *                                  and an empty string will be ignored.
+     * @param[in] aVendorName           The vendor name; must not exceed length of kMaxVendorNameLength
+     *                                  and an empty string will be ignored.
+     * @param[in] aVendorOui            The vendor OUI; must have length of 3 bytes or be empty and ignored.
+     *
+     * @returns OTBR_ERROR_INVALID_ARGS  If aVendorName, aProductName or aVendorOui exceeds the
+     *                                   allowed ranges.
+     * @returns OTBR_ERROR_NONE          If successfully set the meshcop service values.
+     */
+    otbrError SetMeshCopServiceValues(const std::string          &aServiceInstanceName,
+                                      const std::string          &aProductName,
+                                      const std::string          &aVendorName,
+                                      const std::vector<uint8_t> &aVendorOui = {});
+
+    /**
      * This method enables/disables the Border Agent.
      *
      * @param[in] aIsEnabled  Whether to enable the Border Agent.
@@ -124,7 +146,7 @@
     void HandleThreadStateChanged(otChangedFlags aFlags);
 
     bool        IsThreadStarted(void) const;
-    std::string BaseServiceInstanceName() const;
+    std::string GetServiceInstanceNameWithExtAddr(const std::string &aServiceInstanceName) const;
     std::string GetAlternativeServiceInstanceName() const;
 
     otbr::Ncp::ControllerOpenThread &mNcp;
@@ -135,6 +157,20 @@
     std::map<std::string, std::vector<uint8_t>> mMeshCopTxtUpdate;
 #endif
 
+    std::vector<uint8_t> mVendorOui;
+
+    std::string mVendorName;
+    std::string mProductName;
+
+    // The base service instance name typically consists of the vendor and product name. But it can
+    // also be overridden by `OTBR_MESHCOP_SERVICE_INSTANCE_NAME` or method `SetMeshCopServiceValues()`.
+    // For example, this value can be "OpenThread Border Router".
+    std::string mBaseServiceInstanceName;
+
+    // The actual instance name advertised in the mDNS service. This is usually the value of
+    // `mBaseServiceInstanceName` plus the Extended Address and optional random number for avoiding
+    // conflicts. For example, this value can be "OpenThread Border Router #7AC3" or
+    // "OpenThread Border Router #7AC3 (14379)".
     std::string mServiceInstanceName;
 };
 
diff --git a/src/common/types.hpp b/src/common/types.hpp
index 7ba0065..a5cd547 100644
--- a/src/common/types.hpp
+++ b/src/common/types.hpp
@@ -414,6 +414,10 @@
     uint32_t mServiceResolutionEmaLatency;   ///< The EMA latency of service resolutions in milliseconds
 };
 
+static constexpr size_t kVendorOuiLength      = 3;
+static constexpr size_t kMaxVendorNameLength  = 24;
+static constexpr size_t kMaxProductNameLength = 24;
+
 } // namespace otbr
 
 #endif // OTBR_COMMON_TYPES_HPP_
diff --git a/src/ncp/ncp_openthread.cpp b/src/ncp/ncp_openthread.cpp
index efd3e7f..84a8601 100644
--- a/src/ncp/ncp_openthread.cpp
+++ b/src/ncp/ncp_openthread.cpp
@@ -296,6 +296,9 @@
 
     otSysDeinit();
     mInstance = nullptr;
+
+    mThreadStateChangedCallbacks.clear();
+    mResetHandlers.clear();
 }
 
 void ControllerOpenThread::HandleStateChanged(otChangedFlags aFlags)
diff --git a/tests/android/Android.bp b/tests/android/Android.bp
index 242eec0..d100717 100644
--- a/tests/android/Android.bp
+++ b/tests/android/Android.bp
@@ -27,6 +27,7 @@
 //
 
 package {
+    default_team: "trendy_team_fwk_thread_network",
     default_applicable_licenses: ["external_ot-br-posix_license"],
 }
 
diff --git a/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java b/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
index adf16d2..b3758ac 100644
--- a/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
+++ b/tests/android/java/com/android/server/thread/openthread/testing/FakeOtDaemonTest.java
@@ -31,13 +31,20 @@
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
 import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_DISABLED;
 import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
+import static com.android.server.thread.openthread.testing.FakeOtDaemon.OT_DEVICE_ROLE_DISABLED;
 
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
+import android.os.IBinder.DeathRecipient;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -50,6 +57,7 @@
 import com.android.server.thread.openthread.INsdPublisher;
 import com.android.server.thread.openthread.IOtDaemonCallback;
 import com.android.server.thread.openthread.IOtStatusReceiver;
+import com.android.server.thread.openthread.MeshcopTxtAttributes;
 import com.android.server.thread.openthread.OtDaemonState;
 
 import org.junit.Before;
@@ -58,6 +66,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -86,13 +95,19 @@
                                     + "642D643961300102D9A00410A245479C836D551B9CA557F7"
                                     + "B9D351B40C0402A0FFF8");
 
-    private static int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
-    private static int DEFAULT_PREFERRED_CHANNEL_MASK = 0;
+    private static final int DEFAULT_SUPPORTED_CHANNEL_MASK = 0x07FFF800; // from channel 11 to 26
+    private static final int DEFAULT_PREFERRED_CHANNEL_MASK = 0;
+    private static final byte[] TEST_VENDOR_OUI = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
+    private static final String TEST_VENDOR_NAME = "test vendor";
+    private static final String TEST_MODEL_NAME = "test model";
+    private static final String TEST_DEFAULT_COUNTRY_CODE = "WW";
 
     private FakeOtDaemon mFakeOtDaemon;
     private TestLooper mTestLooper;
     @Mock private ParcelFileDescriptor mMockTunFd;
     @Mock private INsdPublisher mMockNsdPublisher;
+    @Mock private IOtDaemonCallback mMockCallback;
+    private MeshcopTxtAttributes mOverriddenMeshcopTxts;
 
     @Before
     public void setUp() {
@@ -100,25 +115,51 @@
 
         mTestLooper = new TestLooper();
         mFakeOtDaemon = new FakeOtDaemon(new Handler(mTestLooper.getLooper()));
+        mOverriddenMeshcopTxts = new MeshcopTxtAttributes();
+        mOverriddenMeshcopTxts.vendorName = TEST_VENDOR_NAME;
+        mOverriddenMeshcopTxts.vendorOui = TEST_VENDOR_OUI;
+        mOverriddenMeshcopTxts.modelName = TEST_MODEL_NAME;
     }
 
     @Test
-    public void initialize_succeed_tunFdIsSet() throws Exception {
-        mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher);
+    public void initialize_succeed_argumentsAreSetAndCallbackIsInvoked() throws Exception {
+        mOverriddenMeshcopTxts.vendorName = TEST_VENDOR_NAME;
+        mOverriddenMeshcopTxts.vendorOui = TEST_VENDOR_OUI;
+        mOverriddenMeshcopTxts.modelName = TEST_MODEL_NAME;
 
+        mFakeOtDaemon.initialize(
+                mMockTunFd,
+                true,
+                mMockNsdPublisher,
+                mOverriddenMeshcopTxts,
+                mMockCallback,
+                TEST_DEFAULT_COUNTRY_CODE);
+        mTestLooper.dispatchAll();
+
+        MeshcopTxtAttributes meshcopTxts = mFakeOtDaemon.getOverriddenMeshcopTxtAttributes();
+        assertThat(meshcopTxts).isNotNull();
+        assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
+        assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI);
+        assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
         assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
-    }
-
-    @Test
-    public void initialize_succeed_NsdPublisherIsSet() throws Exception {
-        mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher);
-
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED);
         assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher);
+        assertThat(mFakeOtDaemon.getStateCallback()).isEqualTo(mMockCallback);
+        assertThat(mFakeOtDaemon.getCountryCode()).isEqualTo(TEST_DEFAULT_COUNTRY_CODE);
+        assertThat(mFakeOtDaemon.isInitialized()).isTrue();
+        verify(mMockCallback, times(1)).onStateChanged(any(), anyLong());
+        verify(mMockCallback, times(1)).onBackboneRouterStateChanged(any());
     }
 
     @Test
     public void registerStateCallback_noStateChange_callbackIsInvoked() throws Exception {
-        mFakeOtDaemon.initialize(mMockTunFd, true, mMockNsdPublisher);
+        mFakeOtDaemon.initialize(
+                mMockTunFd,
+                true,
+                mMockNsdPublisher,
+                mOverriddenMeshcopTxts,
+                mMockCallback,
+                TEST_DEFAULT_COUNTRY_CODE);
         final AtomicReference<OtDaemonState> stateRef = new AtomicReference<>();
         final AtomicLong listenerIdRef = new AtomicLong();
         final AtomicReference<BackboneRouterState> bbrStateRef = new AtomicReference<>();
@@ -211,12 +252,12 @@
     }
 
     @Test
-    public void setThreadEnabled_disableThread_succeed() throws Exception {
-        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED);
+    public void setThreadEnabled_enableThread_succeed() throws Exception {
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED);
 
         final AtomicBoolean succeedRef = new AtomicBoolean(false);
         mFakeOtDaemon.setThreadEnabled(
-                false,
+                true,
                 new IOtStatusReceiver.Default() {
                     @Override
                     public void onSuccess() {
@@ -226,7 +267,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(succeedRef.get()).isTrue();
-        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED);
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_ENABLED);
     }
 
     @Test
@@ -281,4 +322,36 @@
         assertThat(succeedRef.get()).isFalse();
         assertThat(errorRef.get()).isEqualTo(OT_ERROR_INVALID_STATE);
     }
+
+    @Test
+    public void terminate_statesAreResetAndDeathCallbackIsInvoked() throws Exception {
+        DeathRecipient mockDeathRecipient = mock(DeathRecipient.class);
+        mFakeOtDaemon.linkToDeath(mockDeathRecipient, 0);
+        mFakeOtDaemon.initialize(
+                mMockTunFd,
+                true,
+                mMockNsdPublisher,
+                mOverriddenMeshcopTxts,
+                mMockCallback,
+                TEST_DEFAULT_COUNTRY_CODE);
+
+        mFakeOtDaemon.terminate();
+        mTestLooper.dispatchAll();
+
+        assertThat(mFakeOtDaemon.isInitialized()).isFalse();
+        OtDaemonState state = mFakeOtDaemon.getState();
+        assertThat(state.isInterfaceUp).isEqualTo(false);
+        assertThat(state.partitionId).isEqualTo(-1);
+        assertThat(state.deviceRole).isEqualTo(OT_DEVICE_ROLE_DISABLED);
+        assertThat(state.activeDatasetTlvs).isEqualTo(new byte[0]);
+        assertThat(state.pendingDatasetTlvs).isEqualTo(new byte[0]);
+        BackboneRouterState bbrState = mFakeOtDaemon.getBackboneRouterState();
+        assertThat(bbrState.multicastForwardingEnabled).isFalse();
+        assertThat(bbrState.listeningAddresses).isEqualTo(new ArrayList<>());
+        assertThat(mFakeOtDaemon.getDeathRecipient()).isNull();
+        assertThat(mFakeOtDaemon.getTunFd()).isNull();
+        assertThat(mFakeOtDaemon.getNsdPublisher()).isNull();
+        assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(OT_STATE_DISABLED);
+        verify(mockDeathRecipient, times(1)).binderDied();
+    }
 }